360 lines
6.0 KiB
Markdown
360 lines
6.0 KiB
Markdown
# backend_overview.md
|
|
|
|
## 1. Purpose
|
|
|
|
This document defines the complete backend architecture and behavior for the Sats Faucet application.
|
|
|
|
The backend is responsible for:
|
|
|
|
* Verifying NIP-98 signed authentication.
|
|
* Enforcing all eligibility rules.
|
|
* Fetching and caching Nostr account data.
|
|
* Enforcing cooldowns and anti-abuse constraints.
|
|
* Generating and locking entropy payout quotes.
|
|
* Executing Lightning payouts via LNbits.
|
|
* Maintaining transparency statistics.
|
|
* Providing operational safety controls.
|
|
|
|
This backend must be production-safe, abuse-resistant, and configurable entirely via environment variables.
|
|
|
|
---
|
|
|
|
## 2. Tech Stack Requirements
|
|
|
|
Recommended stack:
|
|
|
|
* Language: Go, TypeScript (Node/Express), or Python (FastAPI)
|
|
* Database: PostgreSQL
|
|
* Optional: Redis (rate limiting + nonce replay protection)
|
|
* LNbits for Lightning payments
|
|
* Reverse proxy with TLS (nginx or Caddy)
|
|
|
|
The backend must be stateless except for database persistence.
|
|
|
|
---
|
|
|
|
## 3. Environment Variables
|
|
|
|
All logic must be driven via .env configuration.
|
|
|
|
Key categories:
|
|
|
|
### 3.1 Security
|
|
|
|
* JWT_SECRET
|
|
* HMAC_IP_SECRET
|
|
* NIP98_MAX_SKEW_SECONDS
|
|
* NONCE_TTL_SECONDS
|
|
* TRUST_PROXY
|
|
* ALLOWED_ORIGINS
|
|
|
|
### 3.2 Faucet Economics
|
|
|
|
* FAUCET_ENABLED
|
|
* EMERGENCY_STOP
|
|
* FAUCET_MIN_SATS
|
|
* FAUCET_MAX_SATS
|
|
* PAYOUT_WEIGHT_SMALL
|
|
* PAYOUT_WEIGHT_MEDIUM
|
|
* PAYOUT_WEIGHT_LARGE
|
|
* PAYOUT_WEIGHT_JACKPOT
|
|
* PAYOUT_SMALL_SATS
|
|
* PAYOUT_MEDIUM_SATS
|
|
* PAYOUT_LARGE_SATS
|
|
* PAYOUT_JACKPOT_SATS
|
|
* DAILY_BUDGET_SATS
|
|
* MAX_CLAIMS_PER_DAY
|
|
* MIN_WALLET_BALANCE_SATS
|
|
|
|
### 3.3 Eligibility
|
|
|
|
* MIN_ACCOUNT_AGE_DAYS
|
|
* MIN_ACTIVITY_SCORE
|
|
* MIN_NOTES_COUNT
|
|
* MIN_FOLLOWING_COUNT
|
|
* MIN_FOLLOWERS_COUNT
|
|
* ACTIVITY_LOOKBACK_DAYS
|
|
|
|
### 3.4 Cooldowns
|
|
|
|
* COOLDOWN_DAYS
|
|
* IP_COOLDOWN_DAYS
|
|
* MAX_CLAIMS_PER_IP_PER_PERIOD
|
|
|
|
### 3.5 Nostr
|
|
|
|
* NOSTR_RELAYS
|
|
* RELAY_TIMEOUT_MS
|
|
* MAX_EVENTS_FETCH
|
|
* METADATA_CACHE_HOURS
|
|
|
|
### 3.6 LNbits
|
|
|
|
* LNBITS_BASE_URL
|
|
* LNBITS_ADMIN_KEY
|
|
* LNBITS_WALLET_ID
|
|
* DEPOSIT_LIGHTNING_ADDRESS
|
|
* DEPOSIT_LNURLP
|
|
|
|
---
|
|
|
|
## 4. Database Schema
|
|
|
|
### 4.1 users
|
|
|
|
* pubkey (PK)
|
|
* nostr_first_seen_at
|
|
* notes_count
|
|
* followers_count
|
|
* following_count
|
|
* activity_score
|
|
* last_metadata_fetch_at
|
|
* created_at
|
|
* updated_at
|
|
|
|
### 4.2 claims
|
|
|
|
* id (PK)
|
|
* pubkey (FK users.pubkey)
|
|
* claimed_at
|
|
* payout_sats
|
|
* ip_hash
|
|
* payout_destination_hash
|
|
* status (pending, paid, failed)
|
|
* lnbits_payment_hash
|
|
* error_message
|
|
|
|
### 4.3 ip_limits
|
|
|
|
* ip_hash (PK)
|
|
* last_claimed_at
|
|
* claim_count_period
|
|
|
|
### 4.4 quotes
|
|
|
|
* quote_id (PK)
|
|
* pubkey
|
|
* payout_sats
|
|
* created_at
|
|
* expires_at
|
|
* status (active, consumed, expired)
|
|
|
|
### 4.5 daily_stats (optional)
|
|
|
|
* date (PK)
|
|
* total_paid_sats
|
|
* total_claims
|
|
* unique_pubkeys
|
|
|
|
All IP addresses must be stored as HMAC(IP, HMAC_IP_SECRET).
|
|
|
|
---
|
|
|
|
## 5. NIP-98 Authentication
|
|
|
|
Every protected endpoint must:
|
|
|
|
1. Extract NIP-98 header or payload.
|
|
2. Verify signature against pubkey.
|
|
3. Verify HTTP method and URL match signed payload.
|
|
4. Verify timestamp within allowed skew.
|
|
5. Verify nonce not previously used.
|
|
6. Reject if invalid.
|
|
|
|
Nonces must be stored in Redis or DB for NONCE_TTL_SECONDS.
|
|
|
|
---
|
|
|
|
## 6. IP Resolution
|
|
|
|
If TRUST_PROXY=true:
|
|
|
|
* Read first valid IP from X-Forwarded-For.
|
|
Else:
|
|
* Use request remote address.
|
|
|
|
Then:
|
|
|
|
* Hash IP using HMAC_IP_SECRET.
|
|
* Never store raw IP.
|
|
|
|
---
|
|
|
|
## 7. Eligibility Engine
|
|
|
|
Eligibility flow:
|
|
|
|
1. Check FAUCET_ENABLED.
|
|
2. Check EMERGENCY_STOP.
|
|
3. Check LNbits wallet balance >= MIN_WALLET_BALANCE_SATS.
|
|
4. Check pubkey cooldown.
|
|
5. Check IP cooldown.
|
|
6. Fetch or load cached Nostr profile.
|
|
7. Compute account age.
|
|
8. Compute activity score.
|
|
9. Compare with thresholds.
|
|
|
|
Return structured result with denial code if failed.
|
|
|
|
---
|
|
|
|
## 8. Nostr Data Fetching
|
|
|
|
For a given pubkey:
|
|
|
|
1. Query relays in parallel.
|
|
2. Fetch:
|
|
|
|
* kind 0 (metadata)
|
|
* kind 1 (notes)
|
|
* kind 3 (contacts)
|
|
3. Compute:
|
|
|
|
* earliest created_at
|
|
* notes count in ACTIVITY_LOOKBACK_DAYS
|
|
* following count
|
|
|
|
Cache results for METADATA_CACHE_HOURS.
|
|
|
|
If no events found:
|
|
|
|
* Deny as account_too_new.
|
|
|
|
---
|
|
|
|
## 9. Activity Scoring
|
|
|
|
Example scoring logic:
|
|
|
|
* Has metadata: +10
|
|
* Notes >= MIN_NOTES_COUNT: +20
|
|
* Following >= MIN_FOLLOWING_COUNT: +10
|
|
* Followers >= MIN_FOLLOWERS_COUNT: +10
|
|
|
|
Score must be deterministic and logged.
|
|
|
|
---
|
|
|
|
## 10. Entropy Payout System
|
|
|
|
Weighted random selection:
|
|
|
|
1. Build array based on configured weights.
|
|
2. Generate secure random number.
|
|
3. Select payout bucket.
|
|
|
|
Before finalizing quote:
|
|
|
|
* Check daily spend.
|
|
* Check MAX_CLAIMS_PER_DAY.
|
|
|
|
If budget exceeded:
|
|
|
|
* Either deny or downgrade payout to FAUCET_MIN_SATS.
|
|
|
|
---
|
|
|
|
## 11. Claim Flow
|
|
|
|
### 11.1 POST /claim/quote
|
|
|
|
Input:
|
|
|
|
* lightning_address
|
|
|
|
Steps:
|
|
|
|
* Run eligibility engine.
|
|
* If eligible, generate payout.
|
|
* Insert quote record with expiry (e.g., 60 seconds).
|
|
* Return quote_id and payout_sats.
|
|
|
|
### 11.2 POST /claim/confirm
|
|
|
|
Input:
|
|
|
|
* quote_id
|
|
|
|
Steps:
|
|
|
|
* Verify quote exists and active.
|
|
* Re-check cooldown and budget.
|
|
* Execute LNbits payment.
|
|
* Update claim record.
|
|
* Mark quote consumed.
|
|
|
|
Must be idempotent.
|
|
|
|
---
|
|
|
|
## 12. LNbits Integration
|
|
|
|
Payout flow:
|
|
|
|
1. Resolve Lightning address to LNURL.
|
|
2. Fetch LNURL payRequest.
|
|
3. Call callback with amount in millisats.
|
|
4. Handle success/failure response.
|
|
5. Store payment hash.
|
|
|
|
On failure:
|
|
|
|
* Mark claim failed.
|
|
* Do not lock cooldown unless configured.
|
|
|
|
---
|
|
|
|
## 13. Public Endpoints
|
|
|
|
GET /health
|
|
GET /config
|
|
GET /stats
|
|
GET /deposit
|
|
|
|
No authentication required.
|
|
|
|
---
|
|
|
|
## 14. Logging and Monitoring
|
|
|
|
Each claim attempt must log:
|
|
|
|
* pubkey
|
|
* ip_hash
|
|
* eligibility result
|
|
* payout amount
|
|
* payment status
|
|
|
|
Metrics to track:
|
|
|
|
* denial reasons count
|
|
* payout distribution
|
|
* daily spend
|
|
|
|
---
|
|
|
|
## 15. Security Hard Requirements
|
|
|
|
* Strict CORS
|
|
* Rate limit /claim endpoints
|
|
* Nonce replay protection
|
|
* HMAC IP hashing
|
|
* Admin keys never exposed
|
|
* All secrets loaded from env
|
|
|
|
---
|
|
|
|
## 16. Production Readiness Checklist
|
|
|
|
Backend is complete when:
|
|
|
|
* NIP-98 auth fully verified.
|
|
* Pubkey and IP cooldown enforced.
|
|
* Account age check enforced.
|
|
* Activity score enforced.
|
|
* Entropy payout cannot be rerolled.
|
|
* Daily budget cannot be exceeded.
|
|
* LNbits payout works and errors handled safely.
|
|
* Emergency stop disables claims instantly.
|
|
* Logs clearly show denial reasons.
|