InstantAdPay site skeleton: SIWE auth, live chain ledger, join links, buy flow

Zero-dependency Node server on the RM Circle pattern. Chain config lives in
the volume so the same code runs the Amoy dress rehearsal and mainnet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-04 12:25:34 -05:00
commit f053c1befa
20 changed files with 3116 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
// 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="/ledger" data-p="ledger">Live ledger</a>'
+ '<a href="/my" data-p="my">My account</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.style.color = 'var(--ink)';
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;
}
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, $ };
})();
+44
View File
@@ -0,0 +1,44 @@
// Landing page: live ladder, buy buttons, sponsor attribution line.
(async function () {
await IAP.renderNav('home');
const c = await IAP.getConfig();
IAP.$('contractLink').href = c.explorer + '/address/' + c.contract;
const sp = await (await fetch('/api/sponsor')).json();
if (sp.sponsorId) {
const el = IAP.$('sponsorLine');
el.hidden = false;
el.textContent = 'You were invited by member #' + sp.sponsorId + ' — your purchases pay their team, and your own link will do the same for you.';
}
async function loadLadder() {
const { products } = await (await fetch('/api/catalog')).json();
const tb = document.querySelector('#ladder tbody');
tb.innerHTML = '';
const NAMES = { 1: 'Micro', 2: 'Activation', 3: 'Builder', 4: 'Growth', 5: 'Leader' };
for (const p of products) {
const tr = document.createElement('tr');
tr.innerHTML = '<td><b>' + (NAMES[p.id] || 'Package ' + p.id) + '</b></td>'
+ '<td class="num">' + IAP.fmtUsd(p.priceCents) + '</td>'
+ '<td class="num">' + p.creditAmount.toLocaleString() + '</td>'
+ '<td class="num mono">' + (p.costWei ? IAP.fmtPol(p.costWei) + ' POL' : 'paused') + '</td>'
+ '<td><button class="btn small" data-id="' + p.id + '" data-cost="' + (p.costWei || '') + '"'
+ (p.costWei ? '' : ' disabled') + '>Buy</button></td>';
tb.appendChild(tr);
}
tb.querySelectorAll('button[data-id]').forEach(b => b.addEventListener('click', () => buyPack(b)));
}
async function buyPack(btn) {
try {
btn.disabled = true;
IAP.status('Confirm the purchase in your wallet…');
const r = await IAPWallet.buy(Number(btn.dataset.id), sp.sponsorId || 0, btn.dataset.cost);
if (r.receipt.status !== '0x1') throw new Error('Transaction reverted — see the explorer.');
IAP.status('Purchase settled on-chain — credits are yours, payouts delivered. Watch it on the ledger.', 'ok');
IAP.refreshNavWallet();
} catch (e) {
IAP.status('Purchase failed: ' + (e.message || e), 'bad');
} finally { btn.disabled = false; }
}
loadLadder();
})();
+28
View File
@@ -0,0 +1,28 @@
// Live ledger: recent history + SSE stream of new chain events.
(async function () {
await IAP.renderNav('ledger');
const c = await IAP.getConfig();
IAP.$('contractLink').href = c.explorer + '/address/' + c.contract;
const feed = IAP.$('feed');
const { events } = await (await fetch('/api/feed?n=150')).json();
feed.innerHTML = '';
if (!events.length) feed.innerHTML = '<div class="row muted">No activity yet — the first purchase will appear here the moment it lands.</div>';
for (const ev of events) feed.appendChild(IAP.feedRow(ev, c));
try {
const stats = await (await fetch('/api/stats')).json();
IAP.$('statLine').textContent = stats.onchainMembers + ' on-chain member(s)';
} catch (e) {}
const es = new EventSource('/api/feed/live');
es.onopen = () => { const b = IAP.$('liveBadge'); b.textContent = '● live'; };
es.onerror = () => { const b = IAP.$('liveBadge'); b.textContent = 'reconnecting…'; };
es.onmessage = m => {
try {
const ev = JSON.parse(m.data);
feed.prepend(IAP.feedRow(ev, c));
while (feed.children.length > 200) feed.removeChild(feed.lastChild);
} catch (e) {}
};
})();
+67
View File
@@ -0,0 +1,67 @@
// My account: SIWE sign-in, member state, free activation, invite link.
(async function () {
await IAP.renderNav('my');
const $ = IAP.$;
async function render() {
const me = await IAP.refreshNavWallet();
if (!me || !me.signedIn) { $('signinCard').hidden = false; $('memberArea').hidden = true; return; }
$('signinCard').hidden = true;
$('memberArea').hidden = false;
if (me.memberId) {
$('posLine').innerHTML = 'On-chain <b>member #' + me.memberId + '</b><br>wallet <span class="mono">'
+ me.address.slice(0, 8) + '…' + me.address.slice(-6) + '</span>'
+ (me.onchainSponsorId ? '<br>sponsored by member #' + me.onchainSponsorId : '<br>no sponsor (house line)');
$('creditLine').textContent = (me.credits || 0).toLocaleString();
const bc = me.buyerCount || 0;
$('qualLine').innerHTML = '<b>' + bc + '</b> qualifying buyer(s) referred<br>'
+ (bc >= 5 ? '<span class="badge">Level 3 unlocked — full three-level earnings</span>'
: bc >= 2 ? '<span class="badge">Level 2 unlocked</span> · ' + (5 - bc) + ' more for level 3'
: (2 - bc) + ' more ≥$20 buyer(s) unlock level 2');
$('activateCard').hidden = true;
$('inviteLine').textContent = location.origin + '/join/' + me.memberId;
$('copyInvite').hidden = false;
} else {
$('posLine').innerHTML = 'Signed in as <span class="mono">' + me.address.slice(0, 8) + '…' + me.address.slice(-6)
+ '</span><br>free member — not on-chain yet'
+ (me.sponsorId ? '<br>invited by member #' + me.sponsorId : '');
$('creditLine').textContent = '0';
$('qualLine').textContent = 'Activate your payout wallet (or buy any package) to start; referrals who buy ≥$20 packages qualify you.';
$('activateCard').hidden = false;
$('inviteLine').textContent = 'Your link appears after your free on-chain activation.';
$('copyInvite').hidden = true;
}
}
$('signinBtn').addEventListener('click', async () => {
try {
$('signinBtn').disabled = true;
IAP.status('Check your wallet for the free sign-in signature…');
await IAPWallet.signIn();
IAP.status('Signed in.', 'ok');
await render();
} catch (e) { IAP.status((e && e.message) || String(e), 'bad'); }
finally { $('signinBtn').disabled = false; }
});
$('activateBtn').addEventListener('click', async () => {
try {
$('activateBtn').disabled = true;
const me = await (await fetch('/api/me')).json();
IAP.status('Confirm the free activation in your wallet…');
const r = await IAPWallet.activate(me.sponsorId || 0);
if (r.receipt.status !== '0x1') throw new Error('Transaction reverted — see the explorer.');
IAP.status('Payout wallet activated — your invite link is live.', 'ok');
await render();
} catch (e) { IAP.status('Activation failed: ' + ((e && e.message) || e), 'bad'); }
finally { $('activateBtn').disabled = false; }
});
$('copyInvite').addEventListener('click', async () => {
try { await navigator.clipboard.writeText($('inviteLine').textContent); IAP.status('Link copied.', 'ok'); }
catch (e) { IAP.status('Copy failed — select and copy the link text.', 'bad'); }
});
render();
})();
+82
View File
@@ -0,0 +1,82 @@
/* InstantAdPay — site-wide styles.
Identity: "wire-transfer receipt meets neon ledger" — dark bank-slate ground,
electric mint for money-in-motion, warm amber for calls to action. */
:root{
--ground:#0d1420; --panel:#141d2e; --panel2:#1a2538; --line:#26344d;
--ink:#e8eef7; --muted:#8fa1bb; --mint:#3ee6a8; --mint-soft:#10362b;
--amber:#f5b83d; --amber-ink:#0d1420; --bad:#ff8f7d; --mono:"Consolas","JetBrains Mono",monospace;
}
*{box-sizing:border-box}
body{margin:0;background:var(--ground);color:var(--ink);font:16px/1.55 "Segoe UI",system-ui,sans-serif}
a{color:var(--mint);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:1020px;margin:0 auto;padding:0 18px}
/* nav */
nav{border-bottom:1px solid var(--line);background:rgba(13,20,32,.92);position:sticky;top:0;z-index:10}
nav .wrap{display:flex;align-items:center;gap:22px;height:58px}
.logo{font-weight:800;font-size:19px;color:var(--ink)}
.logo b{color:var(--amber)}
nav .links{display:flex;gap:18px;font-size:14.5px;flex:1}
nav a{color:var(--muted)}
nav a:hover{color:var(--ink);text-decoration:none}
#navWallet{font-size:13.5px}
.rehearsal{background:#3d2a52;color:#c9a9f7;text-align:center;font-size:13px;padding:6px 10px}
.rehearsal b{color:#e6d5ff}
/* buttons */
.btn{display:inline-block;background:var(--amber);color:var(--amber-ink);border:0;border-radius:10px;
padding:12px 22px;font-weight:800;font-size:15.5px;cursor:pointer;font-family:inherit}
.btn.sec{background:transparent;color:var(--mint);border:2px solid var(--mint);padding:10px 20px}
.btn.small{padding:8px 14px;font-size:13.5px}
.btn:disabled{opacity:.45;cursor:default}
/* layout blocks */
.hero{padding:64px 0 40px}
.hero h1{font-size:clamp(30px,5.5vw,50px);line-height:1.08;margin:0 0 14px;max-width:640px}
.hero h1 em{color:var(--mint);font-style:normal}
.hero p.lead{color:var(--muted);font-size:18px;max-width:560px;margin:0 0 26px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px;margin:0 0 16px}
.grid{display:grid;gap:16px}
@media(min-width:760px){.grid.c3{grid-template-columns:1fr 1fr 1fr}.grid.c2{grid-template-columns:1fr 1fr}}
h2{font-size:24px;margin:36px 0 14px}
h3{font-size:17px;margin:0 0 8px}
.muted{color:var(--muted)}
.small{font-size:13.5px}
.mono{font-family:var(--mono)}
/* ladder table */
table{width:100%;border-collapse:collapse;font-size:15px}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
th{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:1px}
td.num,th.num{font-variant-numeric:tabular-nums}
tr:last-child td{border-bottom:0}
.tablewrap{overflow-x:auto}
/* ledger feed */
.feed{font-family:var(--mono);font-size:13.5px;line-height:1.7}
.feed .row{padding:7px 10px;border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.feed .row:first-child{background:var(--mint-soft)}
.feed .t-Purchase{color:var(--amber)}
.feed .t-TierPaid{color:var(--mint)}
.feed .t-AdminPaid{color:var(--muted)}
.feed .t-AwardPaid{color:var(--amber)}
.feed .tx a{color:var(--muted);font-size:12px}
.badge{display:inline-block;background:var(--mint-soft);color:var(--mint);border-radius:10px;padding:2px 10px;font-size:12.5px;font-weight:700}
.badge.amber{background:#3c2f10;color:var(--amber)}
/* split diagram strip */
.split{display:flex;gap:8px;margin:14px 0}
.split div{border-radius:8px;padding:10px 6px;text-align:center;font-size:12.5px;font-weight:700}
.split .s50{flex:5;background:var(--mint-soft);color:var(--mint)}
.split .s20{flex:2;background:#173347;color:#6cc4ee}
.split .s10{flex:1;background:#2c2440;color:#b39df1}
.split .sa{flex:2;background:#33290f;color:var(--amber)}
/* status line + toasts */
#status{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;background:var(--panel2);
border:1px solid var(--line);border-radius:12px;padding:12px 20px;font-size:14px;max-width:90vw;
box-shadow:0 8px 30px rgba(0,0,0,.5)}
#status.ok{border-color:var(--mint)}
#status.bad{border-color:var(--bad)}
footer{border-top:1px solid var(--line);margin-top:60px;padding:26px 0;color:var(--muted);font-size:13.5px}
input,select{background:var(--ground);border:1px solid var(--line);color:var(--ink);border-radius:8px;
padding:10px 12px;font-size:14.5px;font-family:inherit}
:focus-visible{outline:2px solid var(--mint);outline-offset:2px}
@media(prefers-reduced-motion:no-preference){
.feed .row:first-child{animation:landed .9s ease}
@keyframes landed{from{background:#1d5c44}to{background:var(--mint-soft)}}
}
+75
View File
@@ -0,0 +1,75 @@
// Wallet plumbing: EIP-1193 connect, chain add/switch, SIWE sign-in, and raw
// calldata builders for the contract's tx functions (no library needed).
window.IAPWallet = (function () {
const SEL_BUY = '0xfd095e97'; // buy(uint32,uint32)
const SEL_ACTIVATE = '0x1a93ec95'; // activate(uint32)
const pad = v => BigInt(v).toString(16).padStart(64, '0');
function eth() {
if (!window.ethereum) throw new Error('No wallet found. Open this page in a browser with MetaMask (or a wallet browser).');
return window.ethereum;
}
async function ensureChain(c) {
const want = '0x' + Number(c.chainId).toString(16);
const cur = await eth().request({ method: 'eth_chainId' });
if (cur === want) return;
try {
await eth().request({ method: 'wallet_switchEthereumChain', params: [{ chainId: want }] });
} catch (e) {
if (e.code !== 4902) throw e;
await eth().request({ method: 'wallet_addEthereumChain', params: [{
chainId: want, chainName: c.chainName, nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
rpcUrls: [c.rpc], blockExplorerUrls: [c.explorer] }] });
}
}
async function connect() {
const c = await IAP.getConfig();
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
await ensureChain(c);
return addr;
}
// SIWE: challenge -> personal_sign -> verify (server sets the session cookie)
async function signIn() {
const addr = await connect();
const ch = await (await fetch('/api/auth/challenge', { method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr }) })).json();
if (ch.error) throw new Error(ch.error);
const sig = await eth().request({ method: 'personal_sign', params: [ch.message, addr] });
const r = await (await fetch('/api/auth/verify', { method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr, signature: sig }) })).json();
if (r.error) throw new Error(r.error);
return r; // {address, memberId, sponsorId}
}
async function sendTx(data, valueWei) {
const c = await IAP.getConfig();
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
await ensureChain(c);
const tx = { from: addr, to: c.contract, data };
if (valueWei) tx.value = '0x' + BigInt(valueWei).toString(16);
return eth().request({ method: 'eth_sendTransaction', params: [tx] });
}
async function waitTx(hash) {
const c = await IAP.getConfig();
for (let i = 0; i < 60; i++) {
const r = await (await fetch(c.rpc, { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getTransactionReceipt', params: [hash] }) })).json();
if (r.result) return r.result;
await new Promise(res => setTimeout(res, 2500));
}
throw new Error('Timed out waiting for the transaction — check the explorer.');
}
// buy: quote is read live server-side; pad 2% for oracle drift, contract
// refunds every wei of excess in the same transaction.
async function buy(productId, sponsorId, costWei) {
const value = BigInt(costWei) + BigInt(costWei) / 50n;
const data = SEL_BUY + pad(productId) + pad(sponsorId || 0);
const hash = await sendTx(data, value);
return { hash, receipt: await waitTx(hash) };
}
async function activate(sponsorId) {
const data = SEL_ACTIVATE + pad(sponsorId || 0);
const hash = await sendTx(data, null);
return { hash, receipt: await waitTx(hash) };
}
return { connect, signIn, buy, activate, waitTx };
})();
+88
View File
@@ -0,0 +1,88 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>InstantAdPay — advertising that pays instantly, on-chain</title>
<meta name="description" content="Members-only advertising with immutable on-chain settlement. Every package purchase pays the people who built the audience — in the same transaction, verifiable by anyone.">
<link rel="stylesheet" href="/assets/site.css">
</head>
<body>
<div class="wrap">
<section class="hero">
<h1>Advertising that pays the people who build it — <em>instantly, on-chain</em>.</h1>
<p class="lead">Free to join. Real ad inventory. And when anyone buys an ad package,
the payment splits to their sponsors <b>in the same blockchain transaction</b> —
no balances, no withdrawal requests, no company holding your money. Ever.</p>
<p>
<a class="btn" href="/my">Join free</a>
<a class="btn sec" href="/ledger">Watch payments land live</a>
</p>
<p class="small muted" id="sponsorLine" hidden></p>
</section>
<h2>Where every dollar goes — enforced by code, not promises</h2>
<div class="card">
<div class="split">
<div class="s50">50%<br>direct sponsor</div>
<div class="s20">20%<br>level 2</div>
<div class="s10">10%<br>level 3</div>
<div class="sa">20%<br>platform</div>
</div>
<p class="muted">These percentages are <b>constants in an immutable smart contract</b> — there is no
function to change them, pause payouts, or hold funds. The contract's balance is zero after every
sale because everything is delivered the moment it arrives. Don't take our word for it:
every payment is public on the <a href="/ledger">live ledger</a> with a verify link to the blockchain.</p>
</div>
<div class="grid c3">
<div class="card"><h3>🆓 Join free, earn from day one</h3>
<p class="muted small">Membership costs nothing. Share your link and you earn 50% of every ad package
your referrals ever buy — not once, every time. Payment arrives in your own wallet within seconds
of their purchase.</p></div>
<div class="card"><h3>📣 Real advertising, on real audiences</h3>
<p class="muted small">Packages buy ad credits delivered across our owned network — banner, text, and
login placements seen by active members. Credits are recorded on-chain and only ever spent by
your own campaigns.</p></div>
<div class="card"><h3>🔎 Qualification by performance</h3>
<p class="muted small">Deeper earning levels unlock by referring real buyers — 2 buyers unlock level 2,
5 unlock level 3. No buying your way in, no timers, no demotions. When someone in your line isn't
qualified, their share passes up to the next person who is.</p></div>
</div>
<h2>The ad packages</h2>
<div class="card">
<div class="tablewrap">
<table id="ladder">
<thead><tr><th>Package</th><th class="num">Price</th><th class="num">Ad credits</th><th class="num">Cost right now</th><th></th></tr></thead>
<tbody><tr><td colspan="5" class="muted">Loading live prices from the contract…</td></tr></tbody>
</table>
</div>
<p class="small muted">Prices are set in dollars and settled in POL at the live exchange rate the
moment you buy (Chainlink oracle). Overpayment from rate movement is refunded in the same transaction.
Packages of $20 or more count toward your sponsor's qualification.</p>
</div>
<h2>Why this is different</h2>
<div class="grid c2">
<div class="card"><h3>No trust required</h3>
<p class="muted small">Most affiliate platforms ask you to trust their dashboard number and their
payout schedule. Here there is no dashboard number to trust — your earnings arrive as blockchain
transactions to your own wallet, and the contract that sends them cannot be modified by anyone,
including us.</p></div>
<div class="card"><h3>Honest about what it is</h3>
<p class="muted small">This is advertising with a referral program, not an investment. Nobody earns
without real ad purchases happening, and no income is guaranteed. What we guarantee is the part
code can guarantee: if a purchase happens in your line, your share reaches your wallet — instantly,
or it visibly passes to someone qualified.</p></div>
</div>
<footer>
<div>InstantAdPay · every payment verifiable on-chain · <a href="/ledger">live ledger</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">view the contract ↗</a></div>
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible — never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js"></script>
<script src="/assets/wallet.js"></script>
<script src="/assets/home.js"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Live ledger — InstantAdPay</title>
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
<link rel="stylesheet" href="/assets/site.css">
</head>
<body>
<div class="wrap">
<section class="hero" style="padding-bottom:20px">
<h1>The <em>live ledger</em></h1>
<p class="lead">This feed is not our database — it is the blockchain itself, decoded.
Every line has a verify link that opens the raw transaction. If it's not here, it didn't happen;
if it is here, nobody can undo it.</p>
<p><span class="badge" id="liveBadge">connecting…</span>
<span class="small muted" id="statLine"></span></p>
</section>
<div class="card" style="padding:0">
<div class="feed" id="feed"><div class="row muted">Loading recent history…</div></div>
</div>
<footer>
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js"></script>
<script src="/assets/ledger.js"></script>
</body>
</html>
+62
View File
@@ -0,0 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>My account — InstantAdPay</title>
<link rel="stylesheet" href="/assets/site.css">
</head>
<body>
<div class="wrap">
<section class="hero" style="padding-bottom:16px">
<h1>My <em>account</em></h1>
<p class="lead" id="introLead">One free wallet signature signs you in — it cannot move funds or approve anything.
No email, no password.</p>
</section>
<div class="card" id="signinCard">
<h3>Sign in with your wallet</h3>
<p class="muted small">New here? The same button creates your free membership. Your earnings always go
straight to this wallet — we never hold them.</p>
<button class="btn" id="signinBtn">Connect &amp; sign in</button>
</div>
<div id="memberArea" hidden>
<div class="grid c3">
<div class="card"><h3>Your position</h3>
<p id="posLine" class="muted small">…</p></div>
<div class="card"><h3>Ad credits</h3>
<p class="mono" style="font-size:26px;margin:0" id="creditLine">—</p>
<p class="muted small">1 credit = 1¢ of delivery across the network. Recorded on-chain; only your campaigns can spend them.</p></div>
<div class="card"><h3>Qualification</h3>
<p id="qualLine" class="muted small">…</p></div>
</div>
<div class="card" id="activateCard" hidden>
<h3>Activate your payout wallet — free</h3>
<p class="muted small">One free transaction registers this wallet on-chain so commissions can reach it.
Buying any package does this automatically, so you can also just start with a package below.</p>
<button class="btn sec" id="activateBtn">Activate payout wallet</button>
</div>
<div class="card">
<h3>Your invite link</h3>
<p class="muted small">Share it anywhere. Everyone who joins through it becomes part of your line —
you earn 50% of every ad package they ever buy, level 2 and 3 of their teams' buys as you qualify.</p>
<p class="mono" id="inviteLine">Sign in to get your link.</p>
<button class="btn small sec" id="copyInvite" hidden>Copy link</button>
</div>
<div class="card">
<h3>Buy ad packages</h3>
<p class="muted small">The full ladder with live pricing is on the <a href="/">home page</a> —
purchases from this wallet automatically credit this account.</p>
</div>
</div>
<footer><div>InstantAdPay · <a href="/ledger">live ledger</a></div></footer>
</div>
<script src="/assets/common.js"></script>
<script src="/assets/wallet.js"></script>
<script src="/assets/my.js"></script>
</body>
</html>