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:
@@ -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, $ };
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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) {}
|
||||
};
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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)}}
|
||||
}
|
||||
@@ -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 };
|
||||
})();
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 & 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>
|
||||
Reference in New Issue
Block a user