raw_send_to_lnurl asked an LNURL service for an invoice of a specific amount and then quoted and melted whatever invoice came back, without ever checking the two agreed. A malicious or compromised payout service could return an invoice for far more than requested and be paid up to the value of the selected proofs. The melt quote is the mint's own reading of the invoice, so it is compared against the requested amount in the wallet unit before the quote checkpoint and before any proof is reserved: a mismatch now leaves no durable state behind. Destinations were equally unguarded. A bech32 LNURL could decode to a plaintext or internal URL, and both fetches followed redirects blindly, so a public https origin could bounce the request onto loopback or link local metadata addresses. Redirects are now followed manually with the scheme and host re-checked at every hop, and the callback URL taken from the payRequest body is checked the same way. Error messages no longer echo service-controlled response bodies into operator logs. The user refund path reserved proofs and then called raw_send_to_lnurl without an amount, which the callee rejected before dispatch. Every such refund failed with the proofs still locked, and the proof rollback that covers a dispatched melt does not reach that stage. send_to_lnurl now hands over the unreserved available proofs and the amount, leaving reservation to happen only after the destination, the invoice amount and the quote have all been accepted. Pre-dispatch rejection therefore unwinds cleanly, while an ambiguous dispatch still raises MeltOutcomeAmbiguousError and keeps the debit for reconciliation. The missing amount was previously caught by a bare assert, which was both unreachable for the payout callers and the wrong failure mode for the refund caller; it is now an explicit validation.
Routstr Payment Proxy
Routstr is a decentralized protocol for permissionless, private, and censorship-resistant AI inference. It combines Nostr for discovery and Cashu for private Bitcoin micropayments.
This repo contains Routstr Core: a FastAPI-based reverse proxy that sits in front of OpenAI-compatible APIs and handles pay-per-request billing.
Start Here
- Overview: https://docs.routstr.com/overview/
- Provider Guide: https://docs.routstr.com/provider/quickstart/
- User Guide: https://docs.routstr.com/user-guide/introduction/
Basic Usage
If you are a user/developer, you just point an OpenAI-compatible SDK at a Routstr node and pay with a Cashu token.
OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="https://api.routstr.com/v1",
api_key="cashuBo2FteCJodHRwczovL21...",
)
response = client.chat.completions.create(
model="gpt-5-nano",
messages=[{"role": "user", "content": "hello"}],
)
print(response.choices[0].message.content)
cURL
curl https://api.routstr.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-cashu: cashuBo2FteCJodHRwczovL21..." \
-d '{
"model": "gpt-5-nano",
"messages": [{"role": "user", "content": "hello"}]
}'
Quick Start (Docker)
If you are a node runner, start a Routstr Core instance using Docker Compose:
-
Prepare your
.env:# Optional: encrypts node secrets at rest. If unset, the node generates a key # on first start, writes it to routstr_secret.key, and prints it once — back # up that file. Set it explicitly to manage the key yourself (recommended in # production). ROUTSTR_SECRET_KEY=<generated-key> NAME="My AI Node" DESCRIPTION="Fast access to models" RECEIVE_LN_ADDRESS=yourname@wallet.comYour Nostr identity (
nsec) is not set in.env— configure it from the admin UI after first start, where it's stored encrypted in the database. (NSECin.envis still read once as a legacy seed for existing deployments.)If you don't set one, a key is generated and printed on first start — save it somewhere safe (losing it makes previously encrypted secrets unreadable). To supply your own, generate it once and keep it stable:
uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" -
Start the services:
docker compose up -d -
Get your admin password: On first start the node generates an admin password and logs it once with the
/adminURL. Read it from the logs:docker compose logs routstr | grep -i admin(Lost it? Reset with
docker compose exec routstr /.venv/bin/python scripts/reset_admin_password.py --regenerate.) -
Configure: Open http://localhost:8000/admin/ to connect your AI providers and set pricing.
For full instructions, see the Provider Quick Start Guide.
Development
make setup
cp .env.example .env
fastapi run routstr