Branded Voice copy pass, members activity panels, menu update

Site copy regenerated through the bv-tester1 engine; em dashes scrubbed from
all user-visible strings. /api/my/activity serves per-member earnings,
referrals, and purchases from the chain index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-04 12:51:16 -05:00
parent f053c1befa
commit a87a63d75d
10 changed files with 111 additions and 62 deletions
+3 -2
View File
@@ -30,14 +30,15 @@ window.IAP = (function () {
+ '<a class="logo" href="/">Instant<b>AdPay</b></a>' + '<a class="logo" href="/">Instant<b>AdPay</b></a>'
+ '<span class="links">' + '<span class="links">'
+ '<a href="/" data-p="home">How it works</a>' + '<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="/ledger" data-p="ledger">Live ledger</a>'
+ '<a href="/my" data-p="my">My account</a>' + '<a href="/my" data-p="my">Members</a>'
+ '</span><span id="navWallet" class="muted">…</span></div>'; + '</span><span id="navWallet" class="muted">…</span></div>';
document.body.prepend(nav); document.body.prepend(nav);
if (c.rehearsal) { if (c.rehearsal) {
const b = document.createElement('div'); const b = document.createElement('div');
b.className = 'rehearsal'; 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.'; 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); document.body.prepend(b);
} }
const a = nav.querySelector('[data-p="' + active + '"]'); const a = nav.querySelector('[data-p="' + active + '"]');
+3 -3
View File
@@ -8,7 +8,7 @@
if (sp.sponsorId) { if (sp.sponsorId) {
const el = IAP.$('sponsorLine'); const el = IAP.$('sponsorLine');
el.hidden = false; 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.'; 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() { async function loadLadder() {
@@ -33,8 +33,8 @@
btn.disabled = true; btn.disabled = true;
IAP.status('Confirm the purchase in your wallet…'); IAP.status('Confirm the purchase in your wallet…');
const r = await IAPWallet.buy(Number(btn.dataset.id), sp.sponsorId || 0, btn.dataset.cost); 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.'); if (r.receipt.status !== '0x1') throw new Error('Transaction reverted. Check the explorer.');
IAP.status('Purchase settled on-chain — credits are yours, payouts delivered. Watch it on the ledger.', 'ok'); IAP.status('Purchase settled on-chain. Credits are yours, payouts delivered. Watch it on the ledger.', 'ok');
IAP.refreshNavWallet(); IAP.refreshNavWallet();
} catch (e) { } catch (e) {
IAP.status('Purchase failed: ' + (e.message || e), 'bad'); IAP.status('Purchase failed: ' + (e.message || e), 'bad');
+1 -1
View File
@@ -7,7 +7,7 @@
const { events } = await (await fetch('/api/feed?n=150')).json(); const { events } = await (await fetch('/api/feed?n=150')).json();
feed.innerHTML = ''; 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>'; 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)); for (const ev of events) feed.appendChild(IAP.feedRow(ev, c));
try { try {
+22 -5
View File
@@ -16,15 +16,16 @@
$('creditLine').textContent = (me.credits || 0).toLocaleString(); $('creditLine').textContent = (me.credits || 0).toLocaleString();
const bc = me.buyerCount || 0; const bc = me.buyerCount || 0;
$('qualLine').innerHTML = '<b>' + bc + '</b> qualifying buyer(s) referred<br>' $('qualLine').innerHTML = '<b>' + bc + '</b> qualifying buyer(s) referred<br>'
+ (bc >= 5 ? '<span class="badge">Level 3 unlocked — full three-level earnings</span>' + (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' : bc >= 2 ? '<span class="badge">Level 2 unlocked</span> · ' + (5 - bc) + ' more for level 3'
: (2 - bc) + ' more ≥$20 buyer(s) unlock level 2'); : (2 - bc) + ' more ≥$20 buyer(s) unlock level 2');
$('activateCard').hidden = true; $('activateCard').hidden = true;
$('inviteLine').textContent = location.origin + '/join/' + me.memberId; $('inviteLine').textContent = location.origin + '/join/' + me.memberId;
$('copyInvite').hidden = false; $('copyInvite').hidden = false;
loadActivity();
} else { } else {
$('posLine').innerHTML = 'Signed in as <span class="mono">' + me.address.slice(0, 8) + '…' + me.address.slice(-6) $('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' + '</span><br>free member, not on-chain yet'
+ (me.sponsorId ? '<br>invited by member #' + me.sponsorId : ''); + (me.sponsorId ? '<br>invited by member #' + me.sponsorId : '');
$('creditLine').textContent = '0'; $('creditLine').textContent = '0';
$('qualLine').textContent = 'Activate your payout wallet (or buy any package) to start; referrals who buy ≥$20 packages qualify you.'; $('qualLine').textContent = 'Activate your payout wallet (or buy any package) to start; referrals who buy ≥$20 packages qualify you.';
@@ -34,6 +35,22 @@
} }
} }
async function loadActivity() {
try {
const c = await IAP.getConfig();
const a = await (await fetch('/api/my/activity')).json();
const fill = (id, evs, empty) => {
const el = $(id);
el.innerHTML = '';
if (!evs.length) { el.innerHTML = '<div class="row muted">' + empty + '</div>'; return; }
for (const ev of evs) el.appendChild(IAP.feedRow(ev, c));
};
fill('earnFeed', a.earnings, 'No payouts yet. They appear here the moment one lands.');
fill('refFeed', a.referrals, 'No referral activity yet. Share your invite link.');
fill('buyFeed', a.purchases, 'No purchases from this wallet yet.');
} catch (e) {}
}
$('signinBtn').addEventListener('click', async () => { $('signinBtn').addEventListener('click', async () => {
try { try {
$('signinBtn').disabled = true; $('signinBtn').disabled = true;
@@ -51,8 +68,8 @@
const me = await (await fetch('/api/me')).json(); const me = await (await fetch('/api/me')).json();
IAP.status('Confirm the free activation in your wallet…'); IAP.status('Confirm the free activation in your wallet…');
const r = await IAPWallet.activate(me.sponsorId || 0); const r = await IAPWallet.activate(me.sponsorId || 0);
if (r.receipt.status !== '0x1') throw new Error('Transaction reverted — see the explorer.'); if (r.receipt.status !== '0x1') throw new Error('Transaction reverted. Check the explorer.');
IAP.status('Payout wallet activated — your invite link is live.', 'ok'); IAP.status('Payout wallet activated. Your invite link is live.', 'ok');
await render(); await render();
} catch (e) { IAP.status('Activation failed: ' + ((e && e.message) || e), 'bad'); } } catch (e) { IAP.status('Activation failed: ' + ((e && e.message) || e), 'bad'); }
finally { $('activateBtn').disabled = false; } finally { $('activateBtn').disabled = false; }
@@ -60,7 +77,7 @@
$('copyInvite').addEventListener('click', async () => { $('copyInvite').addEventListener('click', async () => {
try { await navigator.clipboard.writeText($('inviteLine').textContent); IAP.status('Link copied.', 'ok'); } 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'); } catch (e) { IAP.status('Copy failed. Select and copy the link text.', 'bad'); }
}); });
render(); render();
+1 -1
View File
@@ -13,7 +13,7 @@ a:hover{text-decoration:underline}
.wrap{max-width:1020px;margin:0 auto;padding:0 18px} .wrap{max-width:1020px;margin:0 auto;padding:0 18px}
/* nav */ /* nav */
nav{border-bottom:1px solid var(--line);background:rgba(13,20,32,.92);position:sticky;top:0;z-index:10} 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} nav .wrap{display:flex;align-items:center;gap:22px;min-height:58px;flex-wrap:wrap;padding-top:6px;padding-bottom:6px}
.logo{font-weight:800;font-size:19px;color:var(--ink)} .logo{font-weight:800;font-size:19px;color:var(--ink)}
.logo b{color:var(--amber)} .logo b{color:var(--amber)}
nav .links{display:flex;gap:18px;font-size:14.5px;flex:1} nav .links{display:flex;gap:18px;font-size:14.5px;flex:1}
+1 -1
View File
@@ -56,7 +56,7 @@ window.IAPWallet = (function () {
if (r.result) return r.result; if (r.result) return r.result;
await new Promise(res => setTimeout(res, 2500)); await new Promise(res => setTimeout(res, 2500));
} }
throw new Error('Timed out waiting for the transaction — check the explorer.'); 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 // buy: quote is read live server-side; pad 2% for oracle drift, contract
// refunds every wei of excess in the same transaction. // refunds every wei of excess in the same transaction.
+34 -34
View File
@@ -2,17 +2,17 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>InstantAdPay — advertising that pays instantly, on-chain</title> <title>InstantAdPay: advertise and earn, locked in code</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."> <meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
<link rel="stylesheet" href="/assets/site.css"> <link rel="stylesheet" href="/assets/site.css">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<section class="hero"> <section class="hero">
<h1>Advertising that pays the people who build it — <em>instantly, on-chain</em>.</h1> <h1>Advertise and earn. <em>Locked in code, not promises.</em></h1>
<p class="lead">Free to join. Real ad inventory. And when anyone buys an ad package, <p class="lead">Buy real ad packages from 5 to 250 dollars. Every purchase settles through
the payment splits to their sponsors <b>in the same blockchain transaction</b> — a smart contract you can read yourself, and your earnings land in
no balances, no withdrawal requests, no company holding your money. Ever.</p> <b>your own wallet</b> before the page even refreshes.</p>
<p> <p>
<a class="btn" href="/my">Join free</a> <a class="btn" href="/my">Join free</a>
<a class="btn sec" href="/ledger">Watch payments land live</a> <a class="btn sec" href="/ledger">Watch payments land live</a>
@@ -20,7 +20,7 @@
<p class="small muted" id="sponsorLine" hidden></p> <p class="small muted" id="sponsorLine" hidden></p>
</section> </section>
<h2>Where every dollar goes — enforced by code, not promises</h2> <h2>Where every dollar goes</h2>
<div class="card"> <div class="card">
<div class="split"> <div class="split">
<div class="s50">50%<br>direct sponsor</div> <div class="s50">50%<br>direct sponsor</div>
@@ -28,25 +28,25 @@
<div class="s10">10%<br>level 3</div> <div class="s10">10%<br>level 3</div>
<div class="sa">20%<br>platform</div> <div class="sa">20%<br>platform</div>
</div> </div>
<p class="muted">These percentages are <b>constants in an immutable smart contract</b> — there is no <p class="muted">Most referral programs run on a database someone can change after you promote.
function to change them, pause payouts, or hold funds. The contract's balance is zero after every This one runs on Polygon. The percentages are written into a contract nobody can touch, not even us.
sale because everything is delivered the moment it arrives. Don't take our word for it: Payouts split 50-20-10 across three levels plus a 20 percent platform fee, every single time, on a
every payment is public on the <a href="/ledger">live ledger</a> with a verify link to the blockchain.</p> <a href="/ledger">public ledger</a> where every payment has a verify link you can click.</p>
</div> </div>
<div class="grid c3"> <div class="grid c3">
<div class="card"><h3>🆓 Join free, earn from day one</h3> <div class="card"><h3>🆓 Join free. Earn from every package your referrals ever buy.</h3>
<p class="muted small">Membership costs nothing. Share your link and you earn 50% of every ad package <p class="muted small">Sign up costs nothing. Zero. Every ad package your direct referrals buy for life
your referrals ever buy — not once, every time. Payment arrives in your own wallet within seconds pays you 50 percent straight to your wallet. Level 2 pays 20 percent. Level 3 pays 10 percent.
of their purchase.</p></div> No balances held, no withdrawal button, no company touching the money.</p></div>
<div class="card"><h3>📣 Real advertising, on real audiences</h3> <div class="card"><h3>📣 Real ads. Real audience. Credits on-chain.</h3>
<p class="muted small">Packages buy ad credits delivered across our owned network — banner, text, and <p class="muted small">Your ad credits are minted on-chain when you buy. One credit equals one cent of
login placements seen by active members. Credits are recorded on-chain and only ever spent by ad delivery across our network of sites. Banner, text, and login ads that only your campaigns
your own campaigns.</p></div> can spend.</p></div>
<div class="card"><h3>🔎 Qualification by performance</h3> <div class="card"><h3>🔎 Qualify by performance. Pass-ups fill the gaps.</h3>
<p class="muted small">Deeper earning levels unlock by referring real buyers — 2 buyers unlock level 2, <p class="muted small">Level 2 unlocks when you bring 2 buyers who have purchased 20 dollars or more.
5 unlock level 3. No buying your way in, no timers, no demotions. When someone in your line isn't Level 3 unlocks at 5 qualified buyers. If someone in your line is not qualified, their share passes
qualified, their share passes up to the next person who is.</p></div> up to the next qualified person automatically. The contract handles it.</p></div>
</div> </div>
<h2>The ad packages</h2> <h2>The ad packages</h2>
@@ -64,21 +64,21 @@
<h2>Why this is different</h2> <h2>Why this is different</h2>
<div class="grid c2"> <div class="grid c2">
<div class="card"><h3>No trust required</h3> <div class="card"><h3>No trust required. Your earnings are a transaction to your wallet.</h3>
<p class="muted small">Most affiliate platforms ask you to trust their dashboard number and their <p class="muted small">Every other platform asks you to trust their ledger. Believe their numbers.
payout schedule. Here there is no dashboard number to trust — your earnings arrive as blockchain Wait for their withdrawal window. InstantAdPay does none of that. The smart contract pays each level
transactions to your own wallet, and the contract that sends them cannot be modified by anyone, at the moment of purchase. Your earnings are a blockchain transaction landing in your own wallet.
including us.</p></div> You can verify every single one on the <a href="/ledger">live ledger</a> with your own eyes.</p></div>
<div class="card"><h3>Honest about what it is</h3> <div class="card"><h3>It is an ad service. That is the only thing it is.</h3>
<p class="muted small">This is advertising with a referral program, not an investment. Nobody earns <p class="muted small">This is an advertising platform with a referral program. Plain and simple.
without real ad purchases happening, and no income is guaranteed. What we guarantee is the part You buy ad credits. You get ads delivered. You refer other advertisers and earn from what they spend.
code can guarantee: if a purchase happens in your line, your share reaches your wallet — instantly, There is no investment pool, no ROI claim, no income promise. What the code guarantees is what the
or it visibly passes to someone qualified.</p></div> code does: split the money and pay it out. That is it.</p></div>
</div> </div>
<footer> <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>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> <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> </footer>
</div> </div>
<script src="/assets/common.js"></script> <script src="/assets/common.js"></script>
+4 -5
View File
@@ -2,17 +2,16 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Live ledger — InstantAdPay</title> <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."> <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"> <link rel="stylesheet" href="/assets/site.css">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<section class="hero" style="padding-bottom:20px"> <section class="hero" style="padding-bottom:20px">
<h1>The <em>live ledger</em></h1> <h1>The ledger <em>does not lie</em>.</h1>
<p class="lead">This feed is not our database — it is the blockchain itself, decoded. <p class="lead">This page streams every payment the contract has ever made. If it is not here,
Every line has a verify link that opens the raw transaction. If it's not here, it didn't happen; it did not happen. Every line carries a verify link straight to the block explorer. Go click one.</p>
if it is here, nobody can undo it.</p>
<p><span class="badge" id="liveBadge">connecting…</span> <p><span class="badge" id="liveBadge">connecting…</span>
<span class="small muted" id="statLine"></span></p> <span class="small muted" id="statLine"></span></p>
</section> </section>
+27 -9
View File
@@ -2,21 +2,21 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>My account — InstantAdPay</title> <title>My account | InstantAdPay</title>
<link rel="stylesheet" href="/assets/site.css"> <link rel="stylesheet" href="/assets/site.css">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<section class="hero" style="padding-bottom:16px"> <section class="hero" style="padding-bottom:16px">
<h1>My <em>account</em></h1> <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. <p class="lead" id="introLead">Sign in with one free wallet signature. No email. No password.
No email, no password.</p> No account to create. The signature is free and cannot move funds.</p>
</section> </section>
<div class="card" id="signinCard"> <div class="card" id="signinCard">
<h3>Sign in with your wallet</h3> <h3>Sign in with your wallet</h3>
<p class="muted small">New here? The same button creates your free membership. Your earnings always go <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> straight to this wallet. We never hold them.</p>
<button class="btn" id="signinBtn">Connect &amp; sign in</button> <button class="btn" id="signinBtn">Connect &amp; sign in</button>
</div> </div>
@@ -32,7 +32,7 @@
</div> </div>
<div class="card" id="activateCard" hidden> <div class="card" id="activateCard" hidden>
<h3>Activate your payout wallet — free</h3> <h3>Activate your payout wallet, free</h3>
<p class="muted small">One free transaction registers this wallet on-chain so commissions can reach it. <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> 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> <button class="btn sec" id="activateBtn">Activate payout wallet</button>
@@ -40,16 +40,34 @@
<div class="card"> <div class="card">
<h3>Your invite link</h3> <h3>Your invite link</h3>
<p class="muted small">Share it anywhere. Everyone who joins through it becomes part of your line — <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> You earn 50 percent of every ad package they ever buy, plus levels 2 and 3 of their teams as you qualify.</p>
<p class="mono" id="inviteLine">Sign in to get your link.</p> <p class="mono" id="inviteLine">Sign in to get your link.</p>
<button class="btn small sec" id="copyInvite" hidden>Copy link</button> <button class="btn small sec" id="copyInvite" hidden>Copy link</button>
</div> </div>
<div class="card"> <div class="card">
<h3>Buy ad packages</h3> <h3>Buy ad packages</h3>
<p class="muted small">The full ladder with live pricing is on the <a href="/">home page</a> — <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> Purchases from this wallet automatically credit this account.</p>
</div>
<h2>Your activity, straight from the chain</h2>
<div class="grid c2">
<div class="card">
<h3>Earnings</h3>
<p class="muted small">Every payout the contract has sent to your wallet. Each line links to the transaction.</p>
<div class="feed" id="earnFeed"><div class="row muted">Nothing yet.</div></div>
</div>
<div class="card">
<h3>Your referrals</h3>
<p class="muted small">Activations and qualifying buys in your line.</p>
<div class="feed" id="refFeed"><div class="row muted">Nothing yet.</div></div>
</div>
</div>
<div class="card">
<h3>Your purchases</h3>
<div class="feed" id="buyFeed"><div class="row muted">Nothing yet.</div></div>
</div> </div>
</div> </div>
+15 -1
View File
@@ -30,7 +30,7 @@ function siteConfig() {
try { saved = JSON.parse(fs.readFileSync(SITE_FILE, 'utf8')); } catch (e) {} try { saved = JSON.parse(fs.readFileSync(SITE_FILE, 'utf8')); } catch (e) {}
return Object.assign({ return Object.assign({
siteName: 'InstantAdPay', siteName: 'InstantAdPay',
tagline: 'Advertising that pays the people who build it — instantly, on-chain.', tagline: 'Advertise and earn. Locked in code, not promises.',
rehearsal: true // shows the testnet banner; flipped off at mainnet launch rehearsal: true // shows the testnet banner; flipped off at mainnet launch
}, saved); }, saved);
} }
@@ -172,6 +172,20 @@ const server = http.createServer(async (req, res) => {
return json(res, 200, out); return json(res, 200, out);
} }
if (p === '/api/my/activity' && req.method === 'GET') {
const s = auth.fromRequest(req);
if (!s) return json(res, 401, { error: 'Sign in first.' });
const id = s.memberId || await auth.refreshMemberId(s);
if (!id) return json(res, 200, { memberId: 0, earnings: [], purchases: [], referrals: [] });
const evs = chain.recentEvents(600);
return json(res, 200, {
memberId: id,
earnings: evs.filter(e => (e.type === 'TierPaid' && e.recipientId === id) || (e.type === 'AwardPaid' && e.toId === id)),
purchases: evs.filter(e => e.type === 'Purchase' && e.buyerId === id),
referrals: evs.filter(e => (e.type === 'MemberActivated' && e.sponsorId === id) || (e.type === 'BuyerCounted' && e.sponsorId === id))
});
}
// -- admin (Bearer ADMIN_PASSWORD) // -- admin (Bearer ADMIN_PASSWORD)
if (p === '/api/admin/site' && req.method === 'PATCH') { if (p === '/api/admin/site' && req.method === 'PATCH') {
if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); if (!isAdmin(req)) return json(res, 401, { error: 'auth' });