Rehearsal faucet: self-serve test-POL so members can actually buy
Fresh wallets hold 0 POL on the rehearsal chain, so nobody could complete a buy. - Oracle: the mock POL/USD feed had gone ~49h stale (quoteWei reverted "Stale oracle" -> catalog costWei null -> packages showed "paused"). Refreshed it and installed a 15-min heartbeat cron so it stays fresh; quotes now return. - New /api/my/faucet (rehearsal-only, rate-limited) tops a connected wallet up to 10 test-POL via anvil_setBalance (no key). "Get test POL" card in the Wallet pane wires it. Members can now fund, buy, and see payouts move on the ledger. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
$('boRehearsal').hidden = false;
|
||||
$('boRehearsal').innerHTML = '<b>Testnet rehearsal</b> · ' + 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…');
|
||||
|
||||
+12
-4
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260907e">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260907g">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -589,6 +589,14 @@
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-wallet" hidden>
|
||||
<div class="card" id="faucetCard" hidden>
|
||||
<h3>Get test POL</h3>
|
||||
<p class="muted small">This is the rehearsal chain, so purchases use valueless <b>test POL</b>, not real money.
|
||||
Tap below to top your connected wallet up to 10 test-POL, then buy a package and watch the payouts
|
||||
move on the live ledger exactly like the real chain will.</p>
|
||||
<p><button class="btn" id="faucetBtn" type="button">Get test POL</button>
|
||||
<span class="small muted" id="faucetInfo"></span></p>
|
||||
</div>
|
||||
<div class="grid c2">
|
||||
<div class="card"><h3>Your account</h3>
|
||||
<p id="posLine" class="muted small">…</p></div>
|
||||
@@ -633,9 +641,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260907e"></script>
|
||||
<script src="/assets/common.js?v=20260907g"></script>
|
||||
<script src="/assets/wallet.js?v=20260907f"></script>
|
||||
<script src="/assets/my.js?v=20260907e"></script>
|
||||
<script src="/assets/chat.js?v=20260907e"></script>
|
||||
<script src="/assets/my.js?v=20260907g"></script>
|
||||
<script src="/assets/chat.js?v=20260907g"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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.' });
|
||||
|
||||
Reference in New Issue
Block a user