From c005e4755bae24f1ed8c08fb012a57ab648616fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Thu, 26 Feb 2026 18:35:48 -0300 Subject: [PATCH] Add full README with setup, API, and env docs Made-with: Cursor --- README.md | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8790c33 --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# Sats Faucet + +A Lightning faucet for the Nostr ecosystem. Nostr users can claim a small, randomized sats payout on a cooldown schedule. The app uses **NIP-98** for authenticated requests, **LNbits** for Lightning payouts, and enforces anti-abuse rules (account age, activity score, per-pubkey and per-IP cooldowns). + +## Features + +- **Nostr auth** — Connect via NIP-07 (browser extension) or external signer; all claim requests are signed with NIP-98. +- **Entropy payouts** — Weighted random payout buckets (e.g. 10, 25, 50, 100 sats); quote is locked so users can’t re-roll by refreshing. +- **Anti-abuse** — Account age (e.g. 14 days), activity score, per-pubkey cooldown (e.g. 7 days), per-IP cooldown and limits, daily budget cap. +- **Transparency** — Public stats: pool balance, total paid, claim counts, recent payouts (anonymized). +- **Deposits** — Lightning address and LNURLp QR for community funding. +- **Operational controls** — `FAUCET_ENABLED`, `EMERGENCY_STOP`, minimum wallet balance guard. + +## Tech stack + +| Layer | Stack | +|----------|--------| +| Frontend | React, TypeScript, Vite, Framer Motion | +| Backend | Node.js, Express, TypeScript | +| Database | SQLite (default) or PostgreSQL | +| Payments | LNbits (Lightning) | +| Auth | NIP-98 (Nostr HTTP Auth) | + +## Prerequisites + +- **Node.js** 18+ +- **LNbits** instance with a wallet and admin key +- **Nostr** signer (e.g. browser extension with NIP-07) for testing claims + +## Quick start + +### 1. Clone and install + +```bash +git clone https://git.azzamo.net/Michilis/SatsFaucet.git +cd SatsFaucet +npm install +cd backend && npm install +cd ../frontend && npm install +``` + +### 2. Backend configuration + +```bash +cd backend +cp .env.example .env +# Edit .env: set HMAC_IP_SECRET, JWT_SECRET, LNbits keys, deposit address, etc. +``` + +Required at minimum: + +- `HMAC_IP_SECRET` — min 32 chars (for IP hashing) +- `JWT_SECRET` — min 32 chars +- `LNBITS_BASE_URL`, `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID` +- `DEPOSIT_LIGHTNING_ADDRESS`, `DEPOSIT_LNURLP` (for the deposit section) + +See `backend/.env.example` for all options (payout weights, cooldowns, eligibility thresholds, Nostr relays). + +### 3. Database + +**SQLite (default):** ensure `backend/data` exists; migrations create the DB. + +```bash +cd backend +npm run migrate +``` + +**PostgreSQL:** set `DATABASE_URL` in `.env` and run: + +```bash +cd backend +npm run migrate +``` + +### 4. Frontend configuration + +```bash +cd frontend +cp .env.example .env +# For local dev, VITE_API_URL=http://localhost:3001 is usually correct +``` + +### 5. Run in development + +**Terminal 1 — backend:** + +```bash +npm run dev:backend +# or: cd backend && npm run dev +``` + +**Terminal 2 — frontend:** + +```bash +npm run dev:frontend +# or: cd frontend && npm run dev +``` + +Open the frontend URL (e.g. `http://localhost:5173`), connect Nostr, enter a Lightning address, and use “Check” then “Confirm” to claim. + +## Production build + +```bash +npm run build +# Builds backend (TypeScript → dist/) and frontend (Vite → frontend/dist/) +``` + +Run the backend (serves API; optionally serve frontend from same host or use a static host): + +```bash +npm start +# or: cd backend && npm start +``` + +Set `TRUST_PROXY=true` when behind a reverse proxy (nginx, Caddy, etc.) so client IPs are read from `X-Forwarded-For`. + +## Project structure + +``` +├── backend/ # Express API +│ ├── src/ +│ │ ├── db/ # SQLite/Pg, migrations, schema +│ │ ├── middleware/ # NIP-98, auth, IP, rate limit +│ │ ├── routes/ # claim, auth, public, user +│ │ └── services/ # eligibility, LNbits, Nostr, quote +│ ├── .env.example +│ └── package.json +├── frontend/ # React SPA +│ ├── src/ +│ │ ├── components/ +│ │ ├── contexts/ +│ │ ├── hooks/ +│ │ ├── pages/ +│ │ └── styles/ +│ ├── .env.example +│ └── package.json +├── context/ # Design/overview docs +├── package.json # Root scripts +└── README.md +``` + +## API overview + +| Method | Endpoint | Auth | Description | +|--------|-------------------|--------|--------------------| +| GET | `/health` | — | Health check | +| GET | `/config` | — | Public config | +| GET | `/stats` | — | Balance, counts | +| GET | `/deposit` | — | Deposit address/QR | +| POST | `/claim/quote` | NIP-98 | Get payout quote | +| POST | `/claim/confirm` | NIP-98 | Confirm and pay | + +Claim flow: frontend calls `POST /claim/quote` with Lightning address; if eligible, backend returns `quote_id` and `payout_sats`. Frontend then calls `POST /claim/confirm` with `quote_id` to execute the payout. Quotes are short-lived to prevent re-rolling. + +## Environment variables (summary) + +- **Security:** `HMAC_IP_SECRET`, `JWT_SECRET`, `NIP98_MAX_SKEW_SECONDS`, `NONCE_TTL_SECONDS`, `TRUST_PROXY`, `ALLOWED_ORIGINS` +- **Faucet:** `FAUCET_ENABLED`, `EMERGENCY_STOP`, payout weights and bucket sats, `DAILY_BUDGET_SATS`, `MIN_WALLET_BALANCE_SATS` +- **Eligibility:** `MIN_ACCOUNT_AGE_DAYS`, `MIN_ACTIVITY_SCORE`, `MIN_NOTES_COUNT`, `MIN_FOLLOWING_COUNT`, etc. +- **Cooldowns:** `COOLDOWN_DAYS`, `IP_COOLDOWN_DAYS`, `MAX_CLAIMS_PER_IP_PER_PERIOD` +- **Nostr:** `NOSTR_RELAYS`, `RELAY_TIMEOUT_MS`, `METADATA_CACHE_HOURS` +- **LNbits:** `LNBITS_BASE_URL`, `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `DEPOSIT_LIGHTNING_ADDRESS`, `DEPOSIT_LNURLP` + +See `backend/.env.example` for full list and defaults. + +## Security notes + +- LNbits keys and secrets stay on the backend; the frontend never sees them. +- IPs are stored only as HMAC hashes (using `HMAC_IP_SECRET`). +- NIP-98 enforces method, URL, timestamp, and nonce; nonces are checked for replay. +- Use HTTPS and a reverse proxy in production; set `TRUST_PROXY` and restrict `ALLOWED_ORIGINS`. + +## License + +MIT (or as specified in the repository).