Hand the sponsor to PolHunter, and put its payouts in the P&L
PolHunter now pays a bounty when a hunter brings someone who turns up and hunts, and the person who gets it has to be the one that member actually joined under here. The sign-in payload carries sponsorRef and the join date across so PolHunter is never guessing from a cookie. Its payouts are real POL from its own faucet and none of it passes through the ad contract, so the P&L could not derive it. It now reads the figures across, cached and fail-soft, and shows finds, bounties and matches as their own card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -434,6 +434,11 @@
|
||||
<div class="card-head"><h3>Where the money went</h3><span class="sub">per period</span></div>
|
||||
<div class="tablewrap"><table class="adm-table" id="pnlSplit"></table></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>PolHunter payouts</h3><span class="sub">all time, its own faucet</span></div>
|
||||
<div class="tablewrap"><table class="adm-table" id="pnlHunt"></table></div>
|
||||
<p class="small muted" style="margin:10px 0 0">Marketing spend, paid in POL from the PolHunter wallet. None of it passes through the ad contract, so it is not in the lines above.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Wallets</h3><span class="sub">live balances</span></div>
|
||||
<div class="tablewrap"><table class="adm-table" id="pnlWallets"></table></div>
|
||||
@@ -488,6 +493,6 @@
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260916a"></script>
|
||||
<script src="/assets/admin.js?v=20260917b"></script>
|
||||
<script src="/assets/admin.js?v=20260923a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -715,6 +715,25 @@
|
||||
['Net after fixed costs', '$' + Math.round(platUsd - fixed).toLocaleString(), 'fixed ' + Math.round(fixed).toLocaleString() + ' over ' + months.toFixed(1) + ' month(s)'],
|
||||
['Pass-ups', r.passedUp.count, r.passedUp.unqualified + ' unqualified · ' + r.passedUp.sendFailed + ' send-failed']
|
||||
].map(t => '<div class="statx"><div><div class="nv" style="font-size:22px">' + esc(String(t[1])) + '</div><div class="lb">' + esc(t[0]) + '</div><span class="chip flat">' + esc(t[2]) + '</span></div></div>').join('');
|
||||
// PolHunter: a marketing cost paid from its own faucet, so it is read across rather than
|
||||
// derived from our chain events (Marty, 2026-09-23)
|
||||
const ph = r.polhunter, phEl = $('pnlHunt');
|
||||
if (phEl) {
|
||||
if (!ph || ph.error || !ph.totals) phEl.innerHTML = '<tr><td colspan="3" class="muted small">PolHunter figures unavailable' + (ph && ph.error ? ' (' + esc(ph.error) + ')' : '') + '.</td></tr>';
|
||||
else {
|
||||
const t = ph.totals, rf = ph.referrals || {}, usd = n => '$' + (Number(n) * px).toFixed(2);
|
||||
const findPol = Math.max(0, Number(t.pol || 0) - Number(rf.paidPol || 0));
|
||||
phEl.innerHTML = '<tr><th>Line</th><th>POL</th><th>USD now</th></tr>'
|
||||
+ [['Mission finds (' + Number(t.paid || 0).toLocaleString() + ' paid)', findPol],
|
||||
['Referral bounties (' + Number(rf.bounties || 0) + ')', null],
|
||||
['Referral matches (' + Number(rf.matches || 0) + ')', null],
|
||||
['Referral payouts total', Number(rf.pol || 0)],
|
||||
['All PolHunter payouts', Number(t.pol || 0)]]
|
||||
.filter(x => x[1] !== null)
|
||||
.map(x => '<tr><td>' + x[0] + '</td><td class="mono">' + Number(x[1]).toFixed(4) + '</td><td class="mono">' + usd(x[1]) + '</td></tr>').join('')
|
||||
+ '<tr><td class="muted small">Hunters paid</td><td class="mono">' + Number(t.hunters || 0) + '</td><td class="muted small">today ' + Number(t.today || 0).toFixed(3) + ' POL</td></tr>';
|
||||
}
|
||||
}
|
||||
$('pnlSplit').innerHTML = '<tr><th>Line</th><th>POL</th><th>USD now</th></tr>'
|
||||
+ [['Level 1 (50%)', r.byTier[1]], ['Level 2 (20%)', r.byTier[2]], ['Level 3 (10%)', r.byTier[3]], ['Platform (20% + pass-ups)', r.platformWei]].map(x => '<tr><td>' + x[0] + '</td><td class="mono">' + pol(x[1]) + '</td><td class="mono">' + usdOf(x[1], px) + '</td></tr>').join('');
|
||||
const W = r.wallets || {}, B = r.balances || {};
|
||||
|
||||
@@ -371,6 +371,23 @@ async function imageCheck(url) {
|
||||
// A video campaign's source has to actually exist. The create form only matched the URL
|
||||
// shape, so a placeholder (yourdomain.com/....mp4) went live on 2026-09-11 and sat there for
|
||||
// eleven days handing every Tier 1 viewer a black player they could never finish.
|
||||
// PolHunter pays real POL out of its own faucet and none of it passes through this contract, so
|
||||
// the P&L cannot derive it from chain events and has to read it across (Marty, 2026-09-23). Cached
|
||||
// and fail-soft: the P&L page must never hang or break because the other site is down.
|
||||
let huntCache = { at: 0, data: null };
|
||||
async function huntLedger() {
|
||||
if (Date.now() - huntCache.at < 120000) return huntCache.data;
|
||||
const base = String(process.env.HUNT_URL || 'https://polhunter.com').replace(/\/+$/, '');
|
||||
try {
|
||||
const r = await fetch(base + '/api/ledger', { signal: AbortSignal.timeout(6000) });
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
const j = await r.json();
|
||||
huntCache = { at: Date.now(), data: { totals: j.totals || null, referrals: j.referrals || null, pools: j.pools || null, at: Date.now() } };
|
||||
} catch (e) {
|
||||
huntCache = { at: Date.now(), data: huntCache.data || { error: e.message } };
|
||||
}
|
||||
return huntCache.data;
|
||||
}
|
||||
async function videoCheck(url) {
|
||||
const u = String(url || '').trim();
|
||||
if (/^\/uploads\//.test(u)) return { ok: true }; // uploaded here, already on our disk
|
||||
@@ -2223,7 +2240,11 @@ const server = http.createServer(async (req, res) => {
|
||||
const memberId = (acct && acct.memberId) || s.memberId || 0;
|
||||
if (!memberId) { res.writeHead(302, { Location: '/my#wallet' }); return res.end(); }
|
||||
const b64u = b => Buffer.from(b).toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
const payload = JSON.stringify({ iat: Date.now(), exp: Date.now() + 5 * 60000, nonce: crypto.randomBytes(12).toString('hex'), memberId: Number(memberId), email: s.email, wallet: (acct && acct.address) || s.address || null, username: (acct && acct.username) || null });
|
||||
// the sponsor rides across too (Marty, 2026-09-23): PolHunter pays a referral bounty, and the
|
||||
// person who gets it has to be the one this member actually joined under here, not a cookie
|
||||
// PolHunter guessed at. sponsorRef is the upline's username or code, matching /join/<ref>.
|
||||
let sponsorRef = (acct && acct.sponsorRef) || '';
|
||||
const payload = JSON.stringify({ iat: Date.now(), exp: Date.now() + 5 * 60000, nonce: crypto.randomBytes(12).toString('hex'), memberId: Number(memberId), email: s.email, wallet: (acct && acct.address) || s.address || null, username: (acct && acct.username) || null, sponsorRef: sponsorRef || null, joinedAt: (acct && acct.created) || null });
|
||||
const tok = b64u(payload) + '.' + b64u(crypto.createHmac('sha256', secret).update(payload).digest());
|
||||
res.writeHead(302, { Location: huntUrl + '/auth?t=' + tok, 'Cache-Control': 'no-store', 'Set-Cookie': 'iap.return=; Path=/; SameSite=Lax; Max-Age=0' + (IS_PROD ? '; Secure' : '') }); return res.end(); // the loop is closed
|
||||
}
|
||||
@@ -3136,6 +3157,7 @@ const server = http.createServer(async (req, res) => {
|
||||
// wallets' live balances and an admin-entered fixed monthly cost
|
||||
if (p === '/api/admin/pnl' && req.method === 'GET') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
const hunt = await huntLedger(); // marketing spend that never touches this contract
|
||||
const days = Math.max(0, Number(u.searchParams.get('days') || 30));
|
||||
let latest = 0; try { latest = parseInt(await chain.rpc('eth_blockNumber', []), 16); } catch (e) {}
|
||||
const fromBlock = days ? latest - Math.round(days * 43200) : 0;
|
||||
@@ -3157,7 +3179,7 @@ const server = http.createServer(async (req, res) => {
|
||||
purchases: { count: purchases.length, volumeWei: sum(purchases, e => e.paidWei).toString(), usdCents: purchases.reduce((n, e) => n + (e.priceCents || 0), 0), byPackage: byPkg },
|
||||
platformWei: sum(admin, e => e.amountWei).toString(), memberPayoutsWei: sum(tier, e => e.amountWei).toString(), byTier,
|
||||
passedUp: { count: passed.length, unqualified: passed.filter(e => e.reason === 'unqualified').length, sendFailed: passed.filter(e => e.reason === 'send-failed').length },
|
||||
wallets, balances, fixedMonthlyUsd: Number(siteConfig().pnlFixedMonthlyUsd) || 0, burner: burner.status() });
|
||||
wallets, balances, fixedMonthlyUsd: Number(siteConfig().pnlFixedMonthlyUsd) || 0, burner: burner.status(), polhunter: hunt });
|
||||
}
|
||||
if (p === '/api/admin/burner' && req.method === 'GET') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
|
||||
Reference in New Issue
Block a user