diff --git a/public/assets/my.js b/public/assets/my.js index f638c92..8672eab 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -9,6 +9,7 @@ $('boRehearsal').hidden = false; $('boRehearsal').innerHTML = 'Testnet rehearsal · ' + c.chainName; } + if (c.rehearsal && $('faucetCard')) $('faucetCard').hidden = false; } catch (e) {} async function api(path, body) { @@ -1191,6 +1192,14 @@ IAP.status('Wallet linked. Earnings pay there from now on.', 'ok'); await render(); })); + if ($('faucetBtn')) $('faucetBtn').addEventListener('click', busy($('faucetBtn'), async () => { + IAP.status('Connect your wallet to receive test POL…'); + const addr = await IAPWallet.connect(); + const r = await api('/api/my/faucet', { address: addr }); + const pol = (Number(BigInt(r.balanceWei) / (10n ** 15n)) / 1000).toFixed(3); + $('faucetInfo').textContent = 'Funded — balance ' + pol + ' test-POL. Head to Buy packages.'; + IAP.status('Your wallet now holds test POL. Go buy a package.', 'ok'); + })); $('activateBtn').addEventListener('click', busy($('activateBtn'), async () => { const me = await (await fetch('/api/me')).json(); IAP.status('Confirm the free activation in your wallet…'); diff --git a/public/my.html b/public/my.html index 9355e45..72c426d 100644 --- a/public/my.html +++ b/public/my.html @@ -4,7 +4,7 @@ Member area | InstantAdPay - + @@ -589,6 +589,14 @@ - + - - + + diff --git a/server.js b/server.js index 29a852b..23413bd 100644 --- a/server.js +++ b/server.js @@ -36,6 +36,7 @@ fs.mkdirSync(UPLOADS_DIR, { recursive: true }); const uploadCounts = new Map(); // email:day -> uploads today const gauntletTokens = new Map(); // email -> welcome-tour token (server-clock dwell floor) const videoTokens = new Map(); // email -> watch-to-earn video token (server-clock watch floor) +const faucetHits = new Map(); // address -> last faucet ts (rehearsal test-POL faucet rate limit) const visitTokens = new Map(); // email -> verified-visit token (dwell + captcha floor) // walk the referral chain upward via sponsorRef (code/username/member id) async function uplineSlides(email, depth = 3) { @@ -681,6 +682,27 @@ const server = http.createServer(async (req, res) => { if (s && s.email) accounts.touchSeen(s.email).catch(() => {}); return json(res, 200, { ok: true }); } + // -- rehearsal test-POL faucet: top a connected wallet up to 10 test-POL + // (anvil_setBalance, no key needed). Rehearsal-only, rate-limited. + if (p === '/api/my/faucet' && req.method === 'POST') { + const s = await auth.fromRequest(req); + if (!s) return json(res, 401, { error: 'Sign in first.' }); + if (!siteConfig().rehearsal) return json(res, 400, { error: 'The faucet is only open during the rehearsal.' }); + const b = await readBody(req); + const addr = String(b.address || '').trim().toLowerCase(); + if (!/^0x[0-9a-f]{40}$/.test(addr)) return json(res, 400, { error: 'Connect your wallet first.' }); + const now = Date.now(), last = faucetHits.get(addr) || 0; + if (now - last < 120000) return json(res, 429, { error: 'Just topped up. Give it a minute, then try again.' }); + try { + const balHex = await chain.rpc('eth_getBalance', [addr, 'latest']); + const bal = BigInt(balHex || '0x0'); + const target = 10n * (10n ** 18n); // 10 test-POL + if (bal < 5n * (10n ** 18n)) await chain.rpc('anvil_setBalance', [addr, '0x' + target.toString(16)]); + faucetHits.set(addr, now); + const nb = await chain.rpc('eth_getBalance', [addr, 'latest']); + return json(res, 200, { ok: true, balanceWei: BigInt(nb || '0x0').toString() }); + } catch (e) { return json(res, 502, { error: 'Faucet is unavailable right now.' }); } + } if (p === '/api/my/chat/send' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });