- Backend: payout is now random between FAUCET_MIN_SATS and FAUCET_MAX_SATS - Remove weighted buckets; simplify config and .env.example - Frontend: show loading and eligibility in main body, modal only for confirm/success - Rename Confirm payout to Claim; add styles for body loading and eligible state Made-with: Cursor
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
import { randomInt } from "crypto";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { config } from "../config.js";
|
|
import { getDb } from "../db/index.js";
|
|
import { getWalletBalanceSats } from "./lnbits.js";
|
|
|
|
const QUOTE_TTL_SECONDS = 60;
|
|
|
|
/**
|
|
* Random payout between FAUCET_MIN_SATS and FAUCET_MAX_SATS (inclusive),
|
|
* capped by daily budget remaining.
|
|
*/
|
|
export function computePayoutForClaim(todayPaidSats: number): number {
|
|
const remaining = Math.max(0, config.dailyBudgetSats - todayPaidSats);
|
|
if (remaining < config.faucetMinSats) return 0;
|
|
const min = Math.max(config.faucetMinSats, 1);
|
|
const max = Math.min(config.faucetMaxSats, remaining);
|
|
if (max < min) return 0;
|
|
return randomInt(min, max + 1);
|
|
}
|
|
|
|
export interface CreateQuoteResult {
|
|
quoteId: string;
|
|
payoutSats: number;
|
|
expiresAt: number;
|
|
}
|
|
|
|
export async function createQuote(pubkey: string, lightningAddress: string): Promise<CreateQuoteResult | null> {
|
|
const db = getDb();
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const dayStart = now - (now % 86400);
|
|
const todayPaid = await db.getPaidSatsSince(dayStart);
|
|
let payout = computePayoutForClaim(todayPaid);
|
|
if (payout <= 0) return null;
|
|
|
|
const walletBalance = await getWalletBalanceSats();
|
|
payout = Math.min(payout, Math.max(0, walletBalance));
|
|
if (payout < config.faucetMinSats) return null;
|
|
|
|
const quoteId = uuidv4();
|
|
const expiresAt = now + QUOTE_TTL_SECONDS;
|
|
await db.createQuote(quoteId, pubkey, payout, lightningAddress, expiresAt);
|
|
return { quoteId, payoutSats: payout, expiresAt };
|
|
}
|