Files
instantadpay/public/assets/common.js
T
martbost 9dc5989aa3 Contract verification page, cache-busted assets, no-withdrawal line
/contract: plain-language security review with the six design laws, honest
operator CAN/CANNOT lists, split walkthrough, oracle bridge and anti-stall
notes, testing summary, live explorer + Sourcify verified-source links
(config-driven so it follows the mainnet flip). Nav gains The contract.
All asset URLs now carry a version query so deploys bust browser caches
(the level-cycler stall). Earn-deeper checklist leads with: no withdrawal
requests, ever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 13:47:49 -05:00

107 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared page runtime: site config, nav, formatting. Zero dependencies.
window.IAP = (function () {
let config = null;
const $ = id => document.getElementById(id);
async function getConfig() {
if (!config) config = await (await fetch('/api/config')).json();
return config;
}
function fmtPol(wei) {
const s = BigInt(wei).toString().padStart(19, '0');
const whole = s.slice(0, -18) || '0';
const frac = s.slice(-18, -12).replace(/0+$/, '');
return whole + (frac ? '.' + frac : '');
}
const fmtUsd = cents => '$' + (cents / 100).toFixed(2);
function status(msg, cls) {
let el = $('status');
if (!el) { el = document.createElement('div'); el.id = 'status'; document.body.appendChild(el); }
el.textContent = msg; el.className = cls || ''; el.hidden = false;
clearTimeout(status._t);
if (cls === 'ok') status._t = setTimeout(() => { el.hidden = true; }, 6000);
}
async function renderNav(active) {
const c = await getConfig();
const nav = document.createElement('nav');
nav.innerHTML = '<div class="wrap">'
+ '<a class="logo" href="/">Instant<b>AdPay</b></a>'
+ '<span class="links">'
+ '<a href="/" data-p="home">How it works</a>'
+ '<a href="/#ladder" data-p="pricing">Ad packages</a>'
+ '<a href="/ledger" data-p="ledger">Live ledger</a>'
+ '<a href="/contract" data-p="contract">The contract</a>'
+ '<a href="/my" data-p="my">Members</a>'
+ '</span><span id="navWallet" class="muted">…</span></div>';
document.body.prepend(nav);
if (c.rehearsal) {
const b = document.createElement('div');
b.className = 'rehearsal';
b.innerHTML = '<b>Testnet rehearsal</b>: running on ' + c.chainName + '. Purchases use valueless test POL while we prove every payout in public.';
document.body.prepend(b);
}
const a = nav.querySelector('[data-p="' + active + '"]');
if (a) a.className = 'active';
refreshNavWallet();
}
async function refreshNavWallet() {
try {
const me = await (await fetch('/api/me')).json();
const el = $('navWallet');
if (!el) return;
if (me.signedIn) {
el.innerHTML = (me.memberId ? '<span class="badge">member #' + me.memberId + '</span> ' : '')
+ '<span class="mono">' + me.address.slice(0, 6) + '…' + me.address.slice(-4) + '</span>';
} else {
el.innerHTML = '<a href="/my">Sign in</a>';
}
return me;
} catch (e) { return null; }
}
function describeEvent(ev, c) {
const pol = w => fmtPol(w) + ' POL';
switch (ev.type) {
case 'Purchase': return '🧾 member #' + ev.buyerId + ' bought package #' + ev.productId
+ ' (' + fmtUsd(ev.priceCents) + ') for ' + pol(ev.paidWei) + ' → +' + ev.creditAmount.toLocaleString() + ' credits';
case 'TierPaid': return '💸 level ' + ev.tier + ' payout → member #' + ev.recipientId + ': ' + pol(ev.amountWei)
+ (ev.hops ? ' (passed up ' + ev.hops + ')' : '');
case 'PassedUp': return '↷ level ' + ev.tier + ' passed over #' + ev.skippedId + ' (' + ev.reason + ')';
case 'AdminPaid': return '🏛 platform fee settled: ' + pol(ev.amountWei);
case 'BuyerCounted': return '⭐ member #' + ev.sponsorId + ' now has ' + ev.newCount + ' qualifying buyer(s)';
case 'MemberActivated': return '👤 member #' + ev.id + ' activated a payout wallet';
case 'AwardPaid': return '🎁 award: ' + pol(ev.amountWei) + ' → member #' + ev.toId;
case 'CreditsConsumed': return '📣 member #' + ev.memberId + ' ran ads: −' + ev.amount.toLocaleString() + ' credits';
case 'PriceCached': return '🔮 oracle price refreshed';
case 'FallbackPriceUsed': return '🔮 cached price bridged an oracle gap';
default: return '· ' + ev.type;
}
}
function feedRow(ev, c) {
const div = document.createElement('div');
div.className = 'row t-' + ev.type;
div.innerHTML = '<span>' + describeEvent(ev, c) + '</span>'
+ '<span class="tx"><a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + ev.tx + '">verify ↗</a></span>';
return div;
}
// Render one served ad into #<elId>. Silent if no inventory.
async function adSlot(type, elId) {
try {
const { ad } = await (await fetch('/api/ads/slot?type=' + type)).json();
const el = $(elId);
if (!ad || !el) return;
if (ad.imageUrl) {
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow">'
+ '<img src="' + ad.imageUrl + '" alt="advertisement" style="max-width:100%;border-radius:8px"></a>'
+ '<div class="small muted">member ad</div>';
} else {
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow"><b>' + ad.title + '</b>'
+ (ad.body ? ' · ' + ad.body : '') + '</a> <span class="small muted">member ad</span>';
}
el.hidden = false;
} catch (e) {}
}
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, $ };
})();