Michilis 5d02d1396f Admin API key auth, account_too_new fixes, clear-all-users cache
- Add ADMIN_API_KEY to config; accept via X-Admin-Key or Bearer (constant-time compare)
- Never serve cached null for nostr_first_seen_at; admin clear-cache and override-age
- Add clearAllUsersCache() and POST /admin/users/clear-cache for all users
- Update .env.example with admin API key and pubkeys comments

Made-with: Cursor
2026-03-16 19:53:05 +00:00
2026-02-26 18:33:00 -03:00
2026-03-16 00:01:44 +00:00

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 cant 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 controlsFAUCET_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

git clone https://git.azzamo.net/Michilis/SatsFaucet.git
cd SatsFaucet
npm install
cd backend && npm install
cd ../frontend && npm install

2. Backend configuration

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.

cd backend
npm run migrate

PostgreSQL: set DATABASE_URL in .env and run:

cd backend
npm run migrate

4. Frontend configuration

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:

npm run dev:backend
# or: cd backend && npm run dev

Terminal 2 — frontend:

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

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):

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

Description
No description provided
Readme 538 KiB
Languages
TypeScript 77.1%
CSS 19.6%
HTML 3.3%