# 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).