// InstantAdPay โ€” membership advertising with immutable on-chain settlement. // Zero-dependency Node server (RM Circle pattern): static pages + JSON API, // wallet sign-in (SIWE), live contract ledger, sponsor join links. // // Chain selection (Amoy rehearsal vs mainnet) lives in data/config.json โ€” // see chain.js. Wipe the volume's accounts/sessions + flip config = launch. const http = require('http'); const https = require('https'); const dns = require('dns'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const sendy = require('./sendy'); const { URL } = require('url'); const chain = require('./chain'); const auth = require('./auth'); const accounts = require('./accounts'); const ads = require('./ads'); const mailer = require('./mailer'); const messages = require('./messages'); const reports = require('./reports'); const drip = require('./drip'); const spaces = require('./spaces'); // DO Spaces video storage (inert unless DO_SPACES_* set) let QR = null; try { QR = require('qrcode'); } catch (e) { /* optional */ } const chatbot = require('./chatbot'); const fraud = require('./fraud'); const coach = require('./coach'); // coaching view, nudges, digest, prospects, link stats const tank = require('./tank'); // holding tank: unsponsored free members, adoptions, pay-it-forward const legacy = require('./legacy'); // Faucet Wave / Tier One Ads bridge: welcome-back credits for listed emails const traffic = require('./traffic'); // public page views by referring domain (admin Traffic tab) const promos = require('./promos'); const missedNotice = require('./missed'); // the whole-chain missed-payout notice // partner promo codes -> free ad credits (link ?promo=CODE or the dashboard box) const blog = require('./blog'); const adminMember = require('./adminmember'); const syndicate = require('./syndicate'); const releases = require('./releases'); const ledger = require('./ledger'); const updates = require('./updates'); const audit = require('./audit'); // counter audit: views vs delivery logs, charges vs shows (Marty, 2026-09-15) // member update emails from Admin > Releases (Marty, 2026-09-14) const leaderboard = require('./leaderboard'); const toolkit = require('./toolkit'); const badge = require('./badge'); // achievement badge image drawn on the server with ffmpeg (2026-09-16) const friday = require('./friday'); const refill = require('./refill'); // campaign running-low / ran-out notices, and what came of them // Five Dollar Friday: +20% credits on Friday packs, the wave, the badge (Marty, 2026-09-20) const snapshot = require('./snapshot'); // daily growth snapshot -> Telegram payments feed (Marty, 2026-09-15) const pipeline = require('./pipeline'); // sponsor follow-up board (coming soon until site setting pipelineMode = on) (Marty, 2026-09-15) const videomaker = require('./videomaker'); // Circuit tool: promo videos with the member's own end card (ffmpeg in the image) // badge-gated promo toolkit + AI Copy Engine (Surge and up) (Marty, 2026-09-14) // referral contest: /leaderboard, Overview card, weekly + monthly winners (Marty, 2026-09-14) // release notes + roadmap: /whats-new, Overview card, Admin > Releases (Marty, 2026-09-14) // blog -> Blotato -> X + Instagram on publish (Marty, 2026-09-13) // admin member card: search, drilldown, edits (Marty, 2026-09-13) // admin-written coaching articles, server-rendered public /blog with SEO metadata (Marty, 2026-09-12) const TRAFFIC_PAGES = new Set(['/', '/ledger', '/contract', '/shorts', '/plays', '/wallets', '/launch', '/partners', '/earning', '/blog', '/whats-new', '/leaderboard']); let tankWaitCache = null; // dashboard: who is waiting for a sponsor (refreshed every minute) const geo = require('./geo'); // viewer country -> tier (DB-IP lite), for campaign targeting const burner = require('./burner'); // automatic on-chain credit burns (inert without ENGINE_KEY) const PORT = Number(process.env.PORT || 3000); const ROOT = __dirname; const PUBLIC_DIR = path.join(ROOT, 'public'); const DATA_DIR = process.env.DATA_DIR || path.join(ROOT, 'data'); const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme'; const ADMIN_EMAIL = String(process.env.ADMIN_EMAIL || '').trim().toLowerCase(); // Admin portal sessions: email-code sign-in allowlisted to ADMIN_EMAIL, kept // in the volume so a restart doesn't log the admin out. Separate cookie and // store from member sessions; the Bearer ADMIN_PASSWORD API path still works. const ADMIN_SESS_FILE = path.join(DATA_DIR, 'admin-sessions.json'); const ADMIN_TTL = 12 * 60 * 60 * 1000; let adminSessions = {}; try { adminSessions = JSON.parse(fs.readFileSync(ADMIN_SESS_FILE, 'utf8')) || {}; } catch (e) { adminSessions = {}; } function saveAdminSessions() { const now = Date.now(); for (const k of Object.keys(adminSessions)) if (!adminSessions[k] || adminSessions[k].expires < now) delete adminSessions[k]; try { fs.writeFileSync(ADMIN_SESS_FILE, JSON.stringify(adminSessions), { mode: 0o600 }); } catch (e) {} } function mintAdminSession(email) { const t = crypto.randomBytes(32).toString('hex'); adminSessions[t] = { email, expires: Date.now() + ADMIN_TTL }; saveAdminSessions(); return t; } function adminTokenOf(req) { const m = /(?:^|;\s*)iap\.adm=([^;]+)/.exec(req.headers.cookie || ''); return m ? decodeURIComponent(m[1]) : null; } function adminFromRequest(req) { const t = adminTokenOf(req); const s = t && adminSessions[t]; return (s && s.expires > Date.now()) ? s : null; } function dropAdminSession(req) { const t = adminTokenOf(req); if (t && adminSessions[t]) { delete adminSessions[t]; saveAdminSessions(); } } function adminCookie(t) { return 'iap.adm=' + encodeURIComponent(t) + '; Path=/; HttpOnly; SameSite=Lax; Max-Age=' + (ADMIN_TTL / 1000) + (IS_PROD ? '; Secure' : ''); } function clearAdminCookie() { return 'iap.adm=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0'; } const IS_PROD = process.env.NODE_ENV === 'production'; const todayCT = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Chicago' }); // Marty's day, never UTC const SITE_FILE = path.join(DATA_DIR, 'site.json'); const db = require('./db'); fs.mkdirSync(DATA_DIR, { recursive: true }); const UPLOADS_DIR = path.join(DATA_DIR, 'uploads'); // solo-ad media lives on the volume fs.mkdirSync(UPLOADS_DIR, { recursive: true }); const uploadCounts = new Map(); // email:day -> uploads today // open earn tokens (ad view / video / visit / tour) live in memory but mirror to the volume so a // redeploy mid-watch does not lose them (2026-09-13: a member's video watch died in a restart) const OPEN_TOKENS_FILE = () => path.join(DATA_DIR, 'open-tokens.json'); const persistedMaps = {}; let tokenSaveTimer = null; function saveOpenTokens() { tokenSaveTimer = null; try { const out = {}; for (const [name, m] of Object.entries(persistedMaps)) out[name] = Object.fromEntries(m); fs.writeFileSync(OPEN_TOKENS_FILE(), JSON.stringify(out)); } catch (e) {} } function persistedMap(name) { const m = new Map(); const touch = () => { if (!tokenSaveTimer) tokenSaveTimer = setTimeout(saveOpenTokens, 500); }; const set = m.set.bind(m), del = m.delete.bind(m); m.set = (k, v) => { set(k, v); touch(); return m; }; m.delete = k => { const r = del(k); if (r) touch(); return r; }; persistedMaps[name] = m; return m; } function loadOpenTokens() { let saved = null; try { saved = JSON.parse(fs.readFileSync(OPEN_TOKENS_FILE(), 'utf8')); } catch (e) { return; } const cutoff = Date.now() - 2 * 3600 * 1000; let n = 0; for (const [name, m] of Object.entries(persistedMaps)) { for (const [k, v] of Object.entries(saved[name] || {})) if (v && Number(v.ts || 0) > cutoff) { Map.prototype.set.call(m, k, v); n++; } } if (n) console.log('open earn tokens restored:', n); } const gauntletTokens = persistedMap('gauntlet'); // email -> welcome-tour token (server-clock dwell floor) const videoTokens = persistedMap('video'); // email -> watch-to-earn video token (server-clock watch floor) const faucetHits = new Map(); // address -> last faucet ts (rehearsal test-POL faucet rate limit) const visitTokens = persistedMap('visit'); // email -> verified-visit token (dwell + captcha floor) // walk the referral chain upward via sponsorRef (code/username/member id) async function uplineSlides(email, depth = 3) { const out = []; let cur = await accounts.byEmail(email); for (let i = 0; i < depth && cur; i++) { const ref = String(cur.sponsorRef || '').trim().toLowerCase(); if (!ref) break; let s = null; if (/^\d+$/.test(ref)) s = await accounts.byMemberId(Number(ref)); if (!s) s = await accounts.byCode(ref); if (!s) s = await accounts.byUsername(ref); if (!s || s.email === cur.email) break; out.push(s); cur = s; } return out; } // Wall ownership ladder: position 1 is always the member's own line banner; // positions 2 and 3 become theirs at 2 and 5 qualifying buyers (the same // thresholds that open payout levels 2 and 3). Until then, or while an unlocked // slot is empty, the slot shows an upline's banner, then a house ad. const catalogCache = { at: 0, products: null }; const wallUnlockedFor = bc => (bc >= 5 ? 3 : bc >= 2 ? 2 : 1); // every on-chain member id this session controls: the main wallet plus linked // positions (Qualified Start). Credits pool across them on the dashboard. const memberInfoCache = new Map(); // id -> { t, v } (60s): line views ask for every member's on-chain row async function cachedMember(id) { const c = memberInfoCache.get(id); if (c && Date.now() - c.t < 60000) return c.v; let v = null; try { v = await chain.member(id); } catch (e) {} memberInfoCache.set(id, { t: Date.now(), v }); return v; } async function myMemberIds(s) { const main = await auth.refreshMemberId(s); const ids = main ? [main] : []; if (s && s.email) for (const p of await accounts.positions(s.email)) if (p.memberId && !ids.includes(p.memberId)) ids.push(p.memberId); return { main, ids }; } function parseWallOffers(a) { try { const v = JSON.parse((a && a.wallOffers) || '[]'); return Array.isArray(v) ? v.slice(0, 2) : []; } catch (e) { return []; } } // admin fallback ads for wall positions 2 & 3 when a member has no upline. // Configurable by dropping data/admin-wall-ads.json ([{name,targetUrl,bannerUrl}]). function getAdminWallAds() { try { const j = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'admin-wall-ads.json'), 'utf8')); if (Array.isArray(j) && j.length) return j; } catch (e) {} return [{ name: 'InstantAdPay', targetUrl: 'https://instantadpay.com/', bannerUrl: null }]; } const chatHits = new Map(); function chatLimited(ip) { const now = Date.now(), rec = chatHits.get(ip); if (!rec || now > rec.reset) { chatHits.set(ip, { count: 1, reset: now + 60000 }); return false; } rec.count += 1; return rec.count > 10; } // magic-code sign-in: emailLower -> {code, exp, tries} const emailCodes = new Map(); // โ”€โ”€ sign-up code guard (Marty, 2026-09-11): the email box is one field and one // tap, so nothing visible stands in a human's way. Bots hit four invisible walls: // a honeypot field, a minimum form age, per-IP + global send limits, and, only // once an IP trips a limit, the same icon check the ad viewer uses. const CODE_LIMITS = { per10m: 5, perDay: 20, globalPerMin: 60, passMs: 5 * 60 * 1000, minFormMs: 2000 }; const codeHits = new Map(); // ip -> { t: [send timestamps, 24h], passUntil, chal: { answer, exp } } const codeGlobal = { minute: 0, n: 0 }; const codeAlert = { last: 0, trips: 0, ips: new Set() }; function clientIp(req) { return String(req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim() || 'unknown'; } // the viewer's country and tier for ad targeting (null tier = unknown: never matches a restricted campaign) function viewerGeo(req) { const cc = geo.countryOf(clientIp(req)); return { cc, tier: geo.tierOf(cc, siteConfig()) }; } function codeChallenge(rec) { const pick = CAPTCHA.slice().sort(() => Math.random() - 0.5).slice(0, 5); const answer = Math.floor(Math.random() * pick.length); rec.chal = { answer: pick[answer][0], exp: Date.now() + 5 * 60 * 1000 }; return { prompt: pick[answer][1], options: pick.map(x => x[0]) }; } // returns null to allow the send, or { status, body } to answer with instead const guardLog = (req, why, b) => console.log('signup-guard', why, clientIp(req), String(b.email || '').replace(/^(.).*(@.*)$/, '$1***$2')); function codeGuard(req, b) { const now = Date.now(); // honeypot: bots fill it, humans never see it. Some form-filler extensions fill every field, // hidden or not (seen 2026-09-11), so a filled honeypot is a CHALLENGE, never a silent drop: // a person passes the icon check and gets the code, a bot cannot. const hpRaw = String(b.hp_field_x9 || '').trim(); const hpIsEmail = !!hpRaw && hpRaw.toLowerCase() === String(b.email || '').trim().toLowerCase(); const hp = !!hpRaw && !hpIsEmail; // 2026-09-13: phones were autofilling the hidden field; own email = autofill, not a bot if (hpRaw) console.log('signup-guard honeypot-value', clientIp(req), hpIsEmail ? 'own-email' : (/^https?:/i.test(hpRaw) ? 'url' : /@/.test(hpRaw) ? 'other-email' : /^\+?[\d\s()-]{6,}$/.test(hpRaw) ? 'phone' : 'text'), 'len=' + hpRaw.length, hpIsEmail ? '' : hpRaw.slice(0, 2) + '***'); const fts = Number(b.fts) || 0; if (!fts || now - fts < CODE_LIMITS.minFormMs) { guardLog(req, 'form-age', b); return { status: 400, body: { error: 'Give the page a second, then tap again.' } }; } if (now - fts > 12 * 3600 * 1000) { guardLog(req, 'form-stale', b); return { status: 400, body: { error: 'This page has been open a long time. Refresh it, then tap again.' } }; } const minute = Math.floor(now / 60000); if (codeGlobal.minute !== minute) { codeGlobal.minute = minute; codeGlobal.n = 0; } if (codeGlobal.n >= CODE_LIMITS.globalPerMin) { guardLog(req, 'global-limit', b); codeTrip(req, 'global'); return { status: 429, body: { error: 'Busy right now. Try again in a minute.' } }; } const ip = clientIp(req); const rec = codeHits.get(ip) || { t: [], passUntil: 0, chal: null }; rec.t = rec.t.filter(ts => now - ts < 24 * 3600 * 1000); const n10 = rec.t.filter(ts => now - ts < 10 * 60 * 1000).length; const limited = hp || n10 >= CODE_LIMITS.per10m || rec.t.length >= CODE_LIMITS.perDay; if (limited && now >= rec.passUntil) { const pick = String(b.pick || ''); if (pick && rec.chal && rec.chal.exp > now && pick === rec.chal.answer) { rec.passUntil = now + CODE_LIMITS.passMs; rec.chal = null; } else { guardLog(req, pick ? 'wrong-pick' : hp ? 'honeypot-challenge' : 'ip-limit', b); if (!hp) codeTrip(req, ip); const challenge = codeChallenge(rec); codeHits.set(ip, rec); return { status: 429, body: { error: pick ? 'That was not it. Try once more.' : 'Quick check before we send another code.', challenge } }; } } rec.t.push(now); codeHits.set(ip, rec); codeGlobal.n += 1; if (codeHits.size > 5000) for (const [k, v] of codeHits) { if (!v.t.length || now - v.t[v.t.length - 1] > 24 * 3600 * 1000) codeHits.delete(k); } return null; } // burst alert: at most one message per 10 minutes, to the admin Telegram chat if set, else the admin email function codeTrip(req, ip) { codeAlert.trips += 1; codeAlert.ips.add(ip); if (Date.now() - codeAlert.last < 10 * 60 * 1000) return; codeAlert.last = Date.now(); const text = '\u26A0\uFE0F InstantAdPay sign-up guard: ' + codeAlert.trips + ' blocked code request' + (codeAlert.trips === 1 ? '' : 's') + ' from ' + codeAlert.ips.size + ' source' + (codeAlert.ips.size === 1 ? '' : 's') + ' (' + [...codeAlert.ips].slice(0, 5).join(', ') + ') in the last window.'; codeAlert.trips = 0; codeAlert.ips = new Set(); const sc = siteConfig(); if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: sign-up guard tripped', text).catch(() => {}); } // earn-view tokens: emailLower -> {token, ts} (one live token per member) const earnTokens = persistedMap('earn'); // human-check pairs for the view verifier: [emoji shown, word named in the prompt] const CAPTCHA = [['๐Ÿš€', 'rocket'], ['โšก', 'lightning bolt'], ['๐Ÿ”‘', 'key'], ['๐ŸŽฏ', 'target'], ['๐ŸŒŠ', 'wave'], ['๐Ÿ”ฅ', 'flame'], ['๐Ÿ’Ž', 'diamond'], ['๐Ÿงฒ', 'magnet'], ['๐Ÿ””', 'bell'], ['๐ŸŒ™', 'moon']]; // โ”€โ”€ frame-breaking check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Surf views frame the advertiser's URL full screen, so a target that refuses // framing (X-Frame-Options / CSP frame-ancestors) would burn members' views on // a blank frame. Catch it the moment the campaign is submitted. The lookup // also refuses private/internal addresses so member URLs can't probe our LAN. const PRIVATE_IP = /^(127\.|10\.|192\.168\.|169\.254\.|0\.|172\.(1[6-9]|2\d|3[01])\.|::1$|::$|f[cd])/i; function frameFetch(url, depth) { return new Promise(resolve => { let u; try { u = new URL(String(url || '')); } catch (e) { return resolve({ error: 'that is not a valid URL' }); } if (!/^https?:$/.test(u.protocol)) return resolve({ error: 'only http(s) URLs work' }); if (u.port && u.port !== '80' && u.port !== '443') return resolve({ error: 'custom ports are not allowed' }); if (u.hostname === 'localhost' || u.hostname.endsWith('.local')) return resolve({ error: 'that address is not reachable from here' }); dns.lookup(u.hostname, (de, addr) => { if (de) return resolve({ error: 'that domain does not resolve' }); if (PRIVATE_IP.test(addr)) return resolve({ error: 'that address is not reachable from here' }); const mod = u.protocol === 'https:' ? https : http; const rq = mod.get(u.href, { timeout: 8000, headers: { 'User-Agent': 'Mozilla/5.0 (compatible; InstantAdPay-FrameCheck/1.0)', Accept: 'text/html' } }, r => { const loc = r.headers.location; r.resume(); // headers are all we need if ([301, 302, 303, 307, 308].includes(r.statusCode) && loc && depth < 4) { rq.destroy(); let next; try { next = new URL(loc, u.href).href; } catch (e2) { return resolve({ error: 'it redirects somewhere invalid' }); } return resolve(frameFetch(next, depth + 1)); // every hop re-runs the private-IP guard } resolve({ status: r.statusCode, xfo: r.headers['x-frame-options'] || '', csp: r.headers['content-security-policy'] || '' }); rq.destroy(); }); rq.on('timeout', () => { rq.destroy(); resolve({ error: 'it did not answer within 8 seconds' }); }); rq.on('error', e2 => resolve({ error: 'it did not answer (' + (e2.code || 'connection failed') + ')' })); }); }); } // shared upload path for member creatives (/api/my/upload) and admin house-ad // creatives (/api/admin/upload): `who` keys the per-day upload counter async function handleUpload(req, res, who) { const ct = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase(); const EXT = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'image/gif': 'gif', 'video/mp4': 'mp4', 'video/webm': 'webm' }; if (!EXT[ct]) return json(res, 400, { error: 'Use a PNG, JPG, WebP, GIF, MP4 or WebM file.' }); const isVideo = ct.startsWith('video/'); const key = who + ':' + new Date().toISOString().slice(0, 10); if ((uploadCounts.get(key) || 0) >= 10) return json(res, 400, { error: 'Upload limit for today reached (10 files).' }); let buf; try { buf = await readRaw(req, isVideo ? 25 * 1024 * 1024 : 3 * 1024 * 1024); } catch (e) { return json(res, 400, { error: 'File too large. Images up to 3MB, video up to 25MB.' }); } const magicOk = buf.length > 16 && ( (ct === 'image/png' && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) || (ct === 'image/jpeg' && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) || (ct === 'image/webp' && buf.slice(0, 4).toString() === 'RIFF' && buf.slice(8, 12).toString() === 'WEBP') || (ct === 'image/gif' && buf.slice(0, 4).toString() === 'GIF8') || (ct === 'video/mp4' && buf.slice(4, 8).toString() === 'ftyp') || (ct === 'video/webm' && buf[0] === 0x1a && buf[1] === 0x45 && buf[2] === 0xdf && buf[3] === 0xa3)); if (!magicOk) return json(res, 400, { error: 'That file does not look like a real ' + EXT[ct].toUpperCase() + '.' }); const name = crypto.randomBytes(12).toString('hex') + '.' + EXT[ct]; uploadCounts.set(key, (uploadCounts.get(key) || 0) + 1); // video goes to DO Spaces when configured (keeps big files off the volume); // images stay local. Falls back to the volume if Spaces isn't set or errors. if (isVideo && spaces.enabled()) { try { const url = await spaces.put('iap-uploads/' + name, buf, ct); return json(res, 200, { url, type: 'video' }); } catch (e) { console.error('spaces put', e.message); /* fall through to volume */ } } fs.writeFileSync(path.join(UPLOADS_DIR, name), buf); return json(res, 200, { url: '/uploads/' + name, type: isVideo ? 'video' : 'image' }); } // lead-capture page hooks (og tags + copy live in public/assets/join.js too) const JOIN_ALIASES = { company: 'martbost', top: 'martbost' }; // neutral partner links -> the top position const JOIN_ANGLES = { // legacy bridge pages (/from/): former members of Marty's closed EvolutionScript sites 'fw-adv': { t: 'Your next ad budget pays you back.', d: 'Faucet Wave closed. InstantAdPay was built for the people who bought ads there: packages from $5, seven formats, every package in your line paid out on Polygon in the same transaction. Welcome-back credits waiting.', url: 'https://instantadpay.com/from/faucetwave?seg=advertiser' }, 'fw-earn': { t: 'Same daily habit. Real payouts on-chain.', d: 'You viewed ads on Faucet Wave. Here you view ads to earn credits, run your own campaign free, and get paid in POL to your own wallet when your line buys ads. Welcome-back credits waiting.', url: 'https://instantadpay.com/from/faucetwave' }, 't1-adv': { t: 'Your next ad budget pays you back.', d: 'Tier One Ads closed. InstantAdPay was built for the people who bought ads there: packages from $5, seven formats, every package in your line paid out on Polygon in the same transaction. Welcome-back credits waiting.', url: 'https://instantadpay.com/from/tieroneads?seg=advertiser' }, 't1-earn': { t: 'Same daily habit. Real payouts on-chain.', d: 'You viewed ads on Tier One Ads. Here you view ads to earn credits, run your own campaign free, and get paid in POL to your own wallet when your line buys ads. Welcome-back credits waiting.', url: 'https://instantadpay.com/from/tieroneads' }, // SocialPix (socialpix.club) gate, 2026-09-20: the media-sharing site was being used as a free // ad board; its signup/post flows now land here. Default copy = advertiser (that is why they came). 'sp-adv': { t: 'Your ad, seen across a whole network.', d: 'SocialPix no longer takes ad posts. Advertising now runs through InstantAdPay: a free account, your ad in seven formats across every site in the network, and every package in your line paid out on Polygon in the same transaction. Real email required.', url: 'https://instantadpay.com/from/socialpix' }, 'sp-earn': { t: 'Same feed. Real payouts on-chain.', d: 'SocialPix has moved its advertising to InstantAdPay. View ads to earn credits, run your own campaign free, and get paid in POL to your own wallet when your line buys ads. Join free by email.', url: 'https://instantadpay.com/from/socialpix?seg=earn' }, instant: { t: 'Paid before the page reloads.', d: 'A smart contract on Polygon splits every ad package the moment it sells. Same transaction, real wallets, public ledger. Join free by email.' }, adspend: { t: 'You were buying ads anyway.', d: 'Here the ad spend in your line pays you, in the same transaction, on a public ledger. Seven formats, packages from $5. Join free.' }, free: { t: 'Watch first. Spend never.', d: 'Join free, view a few ads, earn credits, run your first campaign for zero dollars. Every payout public on Polygon.' }, ledger: { t: 'No back office. No payday.', d: 'Every payout is a public transaction on Polygon you can read yourself. Nothing is ever held. Join free by email.' }, two: { t: 'Two buyers open level two.', d: 'Every direct buyer pays you 50 percent from their first package. Two qualifying buyers open level two, five open level three. Written in a verified contract.' } }; function serveJoinPage(res, tok, angle, ang, setCookies) { let html; try { html = fs.readFileSync(path.join(PUBLIC_DIR, 'join.html'), 'utf8'); } catch (e) { res.writeHead(404, baseHeaders({ 'Content-Type': 'text/plain' })); return res.end('Not found'); } const base = 'https://instantadpay.com'; const url = (ang && ang.url) || (base + '/join/' + tok + (angle ? '?v=' + angle : '')); const title = ang ? ang.t : 'Advertise and earn. Paid on-chain, instantly.'; const desc = ang ? ang.d : 'You are invited to InstantAdPay: real ad packages with same-transaction payouts on Polygon, every payment public. Join free by email.'; const escA = t => String(t).replace(/&/g, '&').replace(/"/g, '"').replace(/' + '' // keeps ?v= so shares stay on the angle + '' + ''; html = html.replace(/[^<]*<\/title>/, '<title>' + escA(title) + ' | InstantAdPay' + og); if (ang) html = html.replace('', ''); // angle pages: squeeze layout from the first paint // Network Ad Space zone on the two legacy squeeze pages only (Marty, 2026-09-19): below the // fine print, above the footer links. The regular join pages stay ad-free. if (/^(fw|t1)-(adv|earn)$/.test(String(angle || ''))) html = html.replace('
', '
' + '
'); const headers = { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store, must-revalidate' }; if (setCookies && setCookies.length) headers['Set-Cookie'] = setCookies; res.writeHead(200, baseHeaders(headers)); res.end(html); } // A banner creative must be an IMAGE (2026-09-16: campaign #134 had the member's join-page link in the // image field and served a broken banner 141 times on the network). Accept /uploads/ files and // image extensions outright; otherwise HEAD the URL and require an image/* content-type. async function imageCheck(url) { const u = String(url || '').trim(); if (/^\/uploads\//.test(u) || /^https?:\/\/instantadpay\.com\/(uploads|banners)\//i.test(u)) return { ok: true }; if (/\.(png|jpe?g|gif|webp|svg)([?#].*)?$/i.test(u)) return { ok: true }; try { const h = await headUrl(u); if (h && /^image\//i.test(String(h.contentType || ''))) return { ok: true }; return { ok: false, reason: 'That link is a web page, not an image. Paste the direct image link (it usually ends in .png or .jpg), or use Promo tools > Banners > Copy image URL.' }; } catch (e) { return { ok: false, reason: 'Could not load that image link. Paste the direct image link (ends in .png or .jpg), or use Promo tools > Banners > Copy image 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 let h; try { h = await headUrl(u); } catch (e) { // no DNS, refused, timed out return { ok: false, reason: 'We could not reach that video link, so members would just see a black player. Check the address, or upload the file here instead.' }; } if (h && (h.status === 404 || h.status === 410)) return { ok: false, reason: 'That video link answers with HTTP ' + h.status + ' โ€” the file is not there. Point the campaign at a file that loads, or upload it here.' }; const ct = String((h && h.contentType) || '').split(';')[0].trim().toLowerCase(); if (ct && !/^video\//.test(ct) && ct !== 'application/octet-stream' && ct !== 'binary/octet-stream') return { ok: false, reason: 'That link returns ' + ct + ', not a video file. Paste the direct .mp4 or .webm link, or upload the file here.' }; return { ok: true }; } function headUrl(url) { return new Promise((resolve, reject) => { let u; try { u = new URL(url); } catch (e) { return reject(new Error('bad url')); } const mod = u.protocol === 'http:' ? require('http') : require('https'); const req = mod.request({ method: 'HEAD', hostname: u.hostname, port: u.port || undefined, path: u.pathname + u.search, timeout: 6000, headers: { 'User-Agent': 'Mozilla/5.0 (compatible; InstantAdPay-ImageCheck/1.0)' } }, r => { if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location && !u.searchParams.has('_r')) { try { const nx = new URL(r.headers.location, url); nx.searchParams.set('_r', '1'); return resolve(headUrl(nx.toString())); } catch (e) {} } resolve({ status: r.statusCode, contentType: r.headers['content-type'] || '' }); r.resume(); }); req.on('error', reject); req.on('timeout', () => req.destroy(new Error('timeout'))); req.end(); }); } async function frameCheck(url) { const h = await frameFetch(url, 0); if (h.error) return { ok: false, reason: 'We checked your URL and ' + h.error + '. Fix the URL and try again.' }; if (h.status >= 400) return { ok: false, reason: 'Your URL answers with HTTP ' + h.status + '. Point the campaign at a working page.' }; if (/deny|sameorigin/i.test(String(h.xfo))) return { ok: false, reason: 'That site blocks framing (X-Frame-Options), so it would show members a blank page in the ad viewer. Use a landing page that allows framing.' }; const fa = /frame-ancestors\s+([^;]+)/i.exec(String(h.csp)); if (fa && !fa[1].split(/\s+/).some(x => { const v = x.replace(/['"]/g, '').toLowerCase(); return v === '*' || v === 'https:' || v.includes('instantadpay.com'); })) return { ok: false, reason: 'That site blocks framing (CSP frame-ancestors), so it would show members a blank page in the ad viewer. Use a landing page that allows framing.' }; return { ok: true }; } async function boot() { await db.init({ dataDir: DATA_DIR }); // no-op without DATABASE_URL (JSON mode) chain.init({ onEvent: ev => { attachNames([ev]).then(a => pushFeed(a[0])).catch(() => pushFeed(ev)); emailOnEvent(ev).catch(() => {}); telegramOnEvent(ev).catch(() => {}); friday.onEvent(ev).catch(e => console.error('friday bonus', e.message)); sponsorSyncOnEvent(ev).catch(e => console.error('sponsor sync', e.message)); } }); auth.init({ dataDir: DATA_DIR, chain, isProd: IS_PROD, site: 'instantadpay.com' }); accounts.init({ dataDir: DATA_DIR }); ads.init({ dataDir: DATA_DIR, chain, tiers: () => geo.tierLists(siteConfig()) }); mailer.init({ dataDir: DATA_DIR }); messages.init({ dataDir: DATA_DIR }); reports.init({ dataDir: DATA_DIR }); drip.init({ dataDir: DATA_DIR, mailer, accounts, chain, site: 'https://instantadpay.com' }); chatbot.init({ dataDir: DATA_DIR, chain }); setTimeout(() => ads.dailySweep().catch(e => console.error('sweep', e.message)), 60 * 1000); setInterval(() => ads.dailySweep().catch(e => console.error('sweep', e.message)), 60 * 60 * 1000); setInterval(() => ads.scheduleSweep().catch(e => console.error('schedule sweep', e.message)), 5 * 60 * 1000); // scheduled starts/ends // follow-up email sequence: send whatever came due (every 10 min, first pass shortly after boot) coach.init({ dataDir: DATA_DIR, chain, accounts, mailer, ads, tank, lb: () => leaderboard, siteConfig }); pipeline.init({ dataDir: DATA_DIR, accounts, coach, chain }); badge.init({ publicDir: PUBLIC_DIR }); snapshot.init({ dataDir: DATA_DIR, db, chain, siteConfig, publicDir: PUBLIC_DIR, send: async (c, t, th) => { await telegramSend(c, t, th); return true; }, sendPhoto: (c, jpeg, cap, th) => telegramSendPhoto(c, jpeg, cap, th) }); friday.init({ dataDir: DATA_DIR, siteConfig, chain, ads, accounts, mailer, drip, telegram: async text => { const sc = siteConfig(); if (!sc.telegramBotToken || !sc.telegramEchoChatId) return false; return telegramSend(sc.telegramEchoChatId, '\u{1F7E0} InstantAdPay \u00b7 ' + text, sc.telegramEchoTopicId); }, notify: async (memberId, subject, text, kind) => { const a = await accounts.byMemberId(memberId); if (!a || !a.email) return; const html = '

' + String(text).replace(/&/g, '&').replace(//g, '>').replace(/(https:\/\/[^\s]+)/g, '$1').split('\n\n').join('

').replace(/\n/g, '
') + '

'; await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [a.email], subject, html, kind || 'notice'); } }); // Campaign refill notices: an advertiser is told when an ad is nearly out and when it has stopped, // with the bonus ladder in the same message, and every send is watched for what they did next. refill.init({ dataDir: DATA_DIR, siteConfig, ads, accounts, chain, mailer, drip, creditsFor: async email => { const v = await adminMember.view(email); return (v && v.credits && v.credits.available) || 0; }, message: async (to, subject, html) => { await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [to], subject, html, 'notice'); }, }); setTimeout(() => refill.tick().catch(e => console.error('refill', e.message)), 90000); setInterval(() => refill.tick().catch(e => console.error('refill', e.message)), 20 * 60 * 1000); setInterval(() => friday.tick().catch(e => console.error('friday', e.message)), 5 * 60 * 1000); setInterval(() => snapshot.tick().catch(e => console.error('snapshot', e.message)), 10 * 60 * 1000); tank.init({ dataDir: DATA_DIR, chain, accounts, messages, mailer, site: 'https://instantadpay.com' }); await fraud.init({ dataDir: DATA_DIR }); { // a suspended account is signed out everywhere: every member route sees no session const realFrom = auth.fromRequest.bind(auth); auth.fromRequest = async (req) => { const sess = await realFrom(req); if (sess && sess.email && fraud.isSuspended(sess.email)) return null; return sess; }; } legacy.init({ dataDir: DATA_DIR }); traffic.init({ dataDir: DATA_DIR }); promos.init({ dataDir: DATA_DIR }); blog.init({ dataDir: DATA_DIR }); adminMember.init({ accounts, ads, chain, tank, legacy, promos, messages, dataDir: DATA_DIR }); loadOpenTokens(); syndicate.init({ dataDir: DATA_DIR, publicDir: PUBLIC_DIR, uploadsDir: UPLOADS_DIR }); releases.init({ dataDir: DATA_DIR }); ledger.init({ dataDir: DATA_DIR }); toolkit.init({ dataDir: DATA_DIR, ads, accounts, siteConfig, coach, messages, promos, videomaker, chain }); updates.init({ dataDir: DATA_DIR, accounts, releases, mailer, drip, sendy, adminEmail: ADMIN_EMAIL }); audit.init({ dataDir: DATA_DIR, notify: text => { const sc = siteConfig(); if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: counter audit', text).catch(() => {}); } }); setTimeout(() => audit.dailyTick(), 5 * 60 * 1000); setInterval(() => audit.dailyTick(), 24 * 60 * 60 * 1000); videomaker.init({ dataDir: DATA_DIR, spaces, accounts }); leaderboard.init({ chain, accounts, ads, fraud, dataDir: DATA_DIR, siteConfig, pushFeed, adminEmail: ADMIN_EMAIL, notify: async text => { const sc = siteConfig(); if (!sc.telegramBotToken || !sc.telegramEchoChatId) return; await telegramSend(sc.telegramEchoChatId, text, sc.telegramEchoTopicId); if (String(sc.leaderboardAnnounceGeneral || '1') !== '0') await telegramSend(sc.telegramEchoChatId, text, null); } }); setTimeout(() => leaderboard.rolloverTick().catch(e => console.error('leaderboard rollover', e.message)), 90 * 1000); setInterval(() => leaderboard.rolloverTick().catch(e => console.error('leaderboard rollover', e.message)), 60 * 60 * 1000); console.log('blog syndication:', syndicate.enabled() ? 'on (X + Instagram via Blotato)' : 'off (no blotato.key)'); setInterval(() => tankNotifyTick().catch(e => console.error('tank notify', e.message)), 15 * 60 * 1000); // new tank arrivals -> Telegram geo.init({ dataDir: DATA_DIR }).catch(e => console.error('geo init', e.message)); setInterval(() => geo.refresh().catch(e => console.error('geo refresh', e.message)), 24 * 3600 * 1000); // monthly file, checked daily setInterval(() => tank.sweep().catch(e => console.error('tank sweep', e.message)), 60 * 60 * 1000); // adoptions past their 7-day window burner.init({ chain, ads, accounts }); setTimeout(() => coach.nudgeTick().catch(e => console.error('coach', e.message)), 90 * 1000); setInterval(() => coach.nudgeTick().catch(e => console.error('coach', e.message)), 60 * 60 * 1000); setTimeout(() => burner.tick().catch(e => console.error('burner', e.message)), 45 * 1000); setInterval(() => burner.tick().catch(e => console.error('burner', e.message)), 5 * 60 * 1000); setTimeout(() => drip.tick().catch(e => console.error('drip', e.message)), 30 * 1000); setInterval(() => drip.tick().catch(e => console.error('drip', e.message)), 10 * 60 * 1000); // NAS reconcile: pull syndicated delivery into the unified credit pool // (inert unless NAS_DB_* is set). Every 5 min after a short warm-up. if (ads.nasEnabled()) { console.log('NAS syndication enabled'); setTimeout(() => ads.reconcileNas().catch(e => console.error('nas reconcile', e.message)), 90 * 1000); setInterval(() => ads.reconcileNas().catch(e => console.error('nas reconcile', e.message)), 5 * 60 * 1000); } } function siteConfig() { let saved = {}; try { saved = JSON.parse(fs.readFileSync(SITE_FILE, 'utf8')); } catch (e) {} return Object.assign({ siteName: 'InstantAdPay', tagline: 'Advertise and earn. Locked in code, not promises.', rehearsal: true, // shows the testnet banner; flipped off at mainnet launch // payment-proof Telegram feed (blank = off) and the P&L pane's fixed monthly cost telegramBotToken: '', telegramChatId: '', telegramTopicId: '', telegramEvents: 'payouts', telegramCtaUrl: 'https://instantadpay.com/', fraudMaxSignupsPerIpDay: 2, fraudBlockSharedDevice: 'on', // anti-fraud: accounts per IP per day; block a second account from the same browser telegramEchoChatId: '', telegramEchoTopicId: '', telegramEchoEvents: 'payouts', // shared cross-program payments topic telegramAdminChatId: '', // private chat for admin alerts (sign-up guard bursts); falls back to ADMIN_EMAIL fridayPromo: '1', // Five Dollar Friday on/off (Marty, 2026-09-20) fridayBonusPct: '20', // bonus credits on any $5+ package bought on a Friday, Central fridayStart: '2026-09-25', // the first Five Dollar Friday launchAt: '', // public launch moment, ISO 8601 with offset (e.g. 2026-09-18T19:00:00-05:00): countdown on /launch + dashboard mark aiCreditsPerGen: 10, aiFreeSurge: 20, aiFreeCircuit: 60, aiFreeNexus: 150, // AI Copy Engine: free generations per month by badge, then credits per generation snapshotEnabled: '1', snapshotHourUtc: '14', snapshotTargets: 'feed,echo', // daily growth snapshot: 14 UTC = 9 AM Central; feed = proof channel, echo = shared payments topic pipelineMode: 'off', // Pipeline board: off = 'coming soon' card, preview = only the admin account sees the live board, on = everyone (Marty, 2026-09-15) pipelineEta: 'Sep 28', // what the coming-soon card and the roadmap promise memberWeeklyEmail: '0', // 1 = the weekly 'Your week on InstantAdPay' email goes to every active member, not only sponsors with a line noPayoutIds: '25', // positions kept for linkage only (compromised wallets): no buys signed from them, no new joins routed under them or their upline chain (Marty, 2026-09-14) leaderboardWeeklyPrize: '', leaderboardMonthlyPrize: '', leaderboardWeeklyCredits: '1000,500,250', leaderboardMonthlyCredits: '5000,2500,1000', leaderboardAnnounceGeneral: '1', // referral contest prizes (text shown on /leaderboard; credits granted to the winner automatically at rollover) legacyCreditsAdvertiser: 500, legacyCreditsEarner: 150, // welcome-back credits for listed Faucet Wave / Tier One Ads emails arriving via /from/ geoTier1: '', // comma-separated ISO country codes; empty = built-in default (US, CA, GB, AU, NZ, IE, DE, FR, NL, SE, NO, DK, FI, CH, AT, BE) geoTier2: '', // empty = built-in default (rest of Western/Central Europe, JP, KR, SG, HK, TW, IL, Gulf, ZA, BR, MX, AR, CL, CO ...); tier 3 = everything else pnlFixedMonthlyUsd: 0 }, saved); } // ---- helpers ---- const MIME = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': 'text/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.webp': 'image/webp', '.ico': 'image/x-icon', '.json': 'application/json', '.mp4': 'video/mp4', '.woff2': 'font/woff2', '.gif': 'image/gif', '.webm': 'video/webm', '.txt': 'text/plain; charset=utf-8', '.xml': 'application/xml; charset=utf-8' }; const CSP = "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.networkadspace.com https://networkadspace.com https://polhunter.com 'sha256-NzvNrqk5jB9YZATwo5BF4JoRlJ02HsnFikbKXgEPdaQ='; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; media-src 'self' https: blob:; connect-src 'self' https://polhunter.com https://*.walletconnect.com wss://*.walletconnect.com https://*.walletconnect.org wss://*.walletconnect.org https://*.reown.com wss://*.reown.com https://*.reown.org wss://*.reown.org https://*.web3modal.org https://*.drpc.org https://*.publicnode.com https://*.coinbase.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.reown.com; form-action 'self'; frame-src https: http:"; function baseHeaders(extra) { return Object.assign({ 'Content-Security-Policy': CSP, 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'strict-origin-when-cross-origin' }, extra || {}); } function json(res, code, obj, extra) { const body = JSON.stringify(obj); res.writeHead(code, baseHeaders(Object.assign({ 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }, extra))); res.end(body); } function sendFile(res, file) { fs.readFile(file, (err, data) => { if (err) { res.writeHead(404, baseHeaders({ 'Content-Type': 'text/plain' })); return res.end('Not found'); } const ext = path.extname(file).toLowerCase(); res.writeHead(200, baseHeaders({ 'Content-Type': MIME[ext] || 'application/octet-stream', // HTML is never stored (so a fresh load always gets the current asset // versions โ€” aggressive in-app wallet browsers were serving stale pages // that pointed at old, since-fixed JS); versioned assets cache for an hour. 'Cache-Control': ext === '.html' ? 'no-store, must-revalidate' : 'public, max-age=3600' })); res.end(data); }); } function readRaw(req, maxBytes) { return new Promise((resolve, reject) => { const chunks = []; let n = 0; req.on('data', c => { n += c.length; if (n > maxBytes) { req.destroy(); reject(new Error('too big')); return; } chunks.push(c); }); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', reject); }); } function readBody(req) { return new Promise((resolve, reject) => { let d = ''; let n = 0; req.on('data', c => { n += c.length; if (n > 64 * 1024) { req.destroy(); reject(new Error('too big')); } d += c; }); req.on('end', () => { try { resolve(d ? JSON.parse(d) : {}); } catch (e) { reject(e); } }); req.on('error', reject); }); } function parseCookies(req) { const out = {}; for (const p of (req.headers.cookie || '').split(';')) { const i = p.indexOf('='); if (i > 0) out[p.slice(0, i).trim()] = decodeURIComponent(p.slice(i + 1).trim()); } return out; } function isAdmin(req) { const h = req.headers.authorization || ''; if (h === 'Bearer ' + ADMIN_PASSWORD) return true; return !!adminFromRequest(req); // /admin portal session } // attach a memberId->username map to events so activity shows real people // Payment trace (Marty, 2026-09-16): every purchase a member is involved in, tier by tier โ€” who was // paid, who was skipped and why (with how many qualifying buyers the skipped person had at that block) // โ€” so "why didn't X get paid on Y's buy" becomes a self-serve lookup instead of a support thread. async function nameForMember(id) { try { const a = await accounts.byMemberId(id); if (a && a.username) return a.username; } catch (e) {} if (db.enabled()) { // a linked extra wallet: name it after its owner try { const r = await db.q('SELECT email FROM positions WHERE member_id=? LIMIT 1', [Number(id)]); if (r.length) { const a = await accounts.byEmail(r[0].email); if (a && a.username) return a.username + ' (linked wallet)'; } } catch (e) {} } return null; } function sponsorMapFromIndex(evs) { const m = new Map(); for (const e of evs) if (e.type === 'MemberActivated' && e.id) m.set(e.id, Number(e.sponsorId) || 0); return m; } function uplineIds(map, id, hops) { const out = []; let cur = map.get(Number(id)) || 0; for (let i = 0; i < hops && cur; i++) { out.push(cur); cur = map.get(cur) || 0; } return out; } async function tracePurchases(targetId, limit) { const id = Number(targetId) || 0; if (!id) return { error: 'Which member?' }; const evs = chain.recentEvents(1e9); // whole history, newest first const byTx = new Map(); for (const e of evs) { if (!e.tx) continue; let g = byTx.get(e.tx); if (!g) { g = []; byTx.set(e.tx, g); } g.push(e); } const counted = evs.filter(e => e.type === 'BuyerCounted'); const buyersBefore = (sid, block) => counted.filter(e => e.sponsorId === sid && e.block < block).length; const out = []; for (const e of evs) { if (e.type !== 'Purchase') continue; const g = byTx.get(e.tx) || []; const paid = g.filter(x => x.type === 'TierPaid' && x.buyerId === e.buyerId); const skipped = g.filter(x => x.type === 'PassedUp' && x.buyerId === e.buyerId); const act = g.find(x => x.type === 'MemberActivated' && x.id === e.buyerId); const admin = g.find(x => x.type === 'AdminPaid' && x.buyerId === e.buyerId); const involved = e.buyerId === id || paid.some(x => x.recipientId === id) || skipped.some(x => x.skippedId === id) || (act && act.sponsorId === id); if (!involved) continue; const tiers = [1, 2, 3].map(t => { const p = paid.find(x => x.tier === t); const sk = skipped.filter(x => x.tier === t); return { tier: t, pct: [50, 20, 10][t - 1], paidTo: p ? p.recipientId : 0, amountWei: p ? p.amountWei : '0', hops: p ? p.hops : 0, skipped: sk.map(x => ({ id: x.skippedId, reason: x.reason, buyersThen: x.reason === 'unqualified' ? buyersBefore(x.skippedId, e.block) : null })) }; }); out.push({ ts: e.ts, block: e.block, tx: e.tx, buyerId: e.buyerId, sponsorId: act ? act.sponsorId : null, priceCents: e.priceCents, paidWei: e.paidWei, tiers, adminWei: admin ? admin.amountWei : '0' }); if (out.length >= (limit || 30)) break; } const ids = new Set([id]); for (const p of out) { ids.add(p.buyerId); if (p.sponsorId) ids.add(p.sponsorId); for (const t of p.tiers) { if (t.paidTo) ids.add(t.paidTo); for (const x of t.skipped) ids.add(x.id); } } const names = {}; for (const i of ids) { const n = await nameForMember(i); if (n) names[i] = n; } const smap = sponsorMapFromIndex(evs); return { memberId: id, name: names[id] || null, activated: smap.has(id), sponsorId: smap.get(id) || 0, buyersNow: counted.filter(e => e.sponsorId === id).length, purchases: out, names }; } async function attachNames(evts) { try { const ids = []; for (const ev of evts) for (const k of ['id', 'buyerId', 'recipientId', 'skippedId', 'sponsorId', 'newBuyerId', 'toId', 'memberId']) if (ev[k]) ids.push(ev[k]); const names = await accounts.namesForMembers(ids); if (!Object.keys(names).length) return evts; return evts.map(ev => Object.assign({}, ev, { names })); } catch (e) { return evts; } } // A sponsor token is a numeric chain id or a site share code. Codes resolve // to the referrer's CURRENT chain id, so activation any time before the // referral's first purchase still locks the line to them. // No-payout positions (siteConfig.noPayoutIds): a member id whose on-chain wallet must never be paid again. // A purchase pays the buyer's three uplines, so a buy from `id` is blocked when id itself or any of its three // uplines is listed; a join under `id` is blocked when id or its two uplines is listed (the new member's buys // would reach the listed wallet at tier 2 or 3). Returns the listed id that would be hit, else 0. function noPayoutSet() { return new Set(String(siteConfig().noPayoutIds || '').split(/[,\s]+/).map(Number).filter(n => n > 0)); } async function payoutChainBlocked(id, hops) { const bad = noPayoutSet(); if (!bad.size || !id) return 0; let cur = Number(id); for (let i = 0; i <= hops && cur; i++) { if (bad.has(cur)) return cur; let m = null; try { m = await chain.member(cur); } catch (e) { break; } cur = m ? Number(m.sponsorId) || 0 : 0; } return 0; } // Why a sponsor token did not resolve matters (vladz79 โ†’ #24 locked under the company on 2026-09-13 because a // lookup came back empty and the code fell back silently): 'ok' | 'none' (no token) | 'unknown' (no such account) // | 'notActivated' (sponsor has no wallet / payouts off) | 'rpc' (chain lookup failed right now). async function resolveSponsorDetailed(tok) { const t = String(tok || '').trim().toLowerCase(); if (!t) return { id: 0, reason: 'none' }; if (/^\d+$/.test(t)) return { id: Number(t), reason: 'ok' }; let acct = await accounts.byCode(t); if (!acct) acct = await accounts.byUsername(t); // vanity links: /join/ if (!acct) return { id: 0, reason: 'unknown', name: t }; if (!acct.address) return { id: 0, reason: 'notActivated', name: acct.username ? '@' + acct.username : t }; try { const id = await chain.memberIdByAccount(acct.address); return { id, reason: id ? 'ok' : 'notActivated', name: acct.username ? '@' + acct.username : t }; } catch (e) { return { id: 0, reason: 'rpc', name: acct.username ? '@' + acct.username : t }; } } async function resolveSponsorToken(tok) { return (await resolveSponsorDetailed(tok)).id; } // 2026-09-16 (Marty): a buyer is never held up because their sponsor has not activated. Walk the // site's sponsor line upward to the first upline who can be paid on-chain (activated, payouts on, // not on the no-payout list); none -> the catch position (#1, Marty's top). The referral then follows // the money: sponsorSyncOnEvent re-points the buyer's account to whoever was paid once the position exists. async function walkUpActivatedSponsor(tok) { const skipped = []; const seen = new Set(); const t = String(tok || '').trim().toLowerCase(); let acct = await accounts.byCode(t); if (!acct) acct = await accounts.byUsername(t); for (let hop = 0; acct && hop < 25 && !seen.has(acct.email); hop++) { seen.add(acct.email); const name = acct.username ? '@' + acct.username : acct.email.replace(/@.*/, '') + '@'; let id = 0; if (acct.address) { try { id = await chain.memberIdByAccount(acct.address); } catch (e) { return { id: 0, name: null, skipped, reason: 'rpc' }; } } if (id && !(await payoutChainBlocked(id, 2))) return { id, name, skipped, reason: 'ok' }; skipped.push({ email: acct.email, name }); acct = await accounts.sponsorOf(acct.email); } return { id: 0, name: null, skipped, reason: 'none' }; } // The feed line for a walk-up: who had not switched on payouts, whose referral (and purchase, when the // activation and the buy share a tx) routed past them, and who it belongs to now. async function postWalkUp(buyerId, oldRef, toId, pur) { const sc = siteConfig(); if (!sc.telegramBotToken || (!sc.telegramChatId && !sc.telegramEchoChatId)) return; const lostAcct = await refAccount(oldRef); if (!lostAcct) return; const lostName = lostAcct.username ? '@' + lostAcct.username : lostAcct.email.replace(/@.*/, '') + '@'; const nm = await accounts.namesForMembers([buyerId, toId]).catch(() => ({})); const who = id => '#' + id + (nm[id] ? ' @' + nm[id] : ''); const site = 'https://instantadpay.com/my'; const what = pur ? who(buyerId) + "'s $" + Math.round(pur.priceCents / 100) + ' purchase routed past them' : who(buyerId) + ' switched on payouts and their position routed past them'; const missed = '\u{1F62C} Missed sale: ' + lostName + ' had not switched on payouts, so ' + what + '. That member, and every payout from them, now belongs to ' + who(toId) + '.' + '\nTwo minutes on the Wallet tab prevents this: ' + site.replace(/^https:\/\//, '') + ''; if (sc.telegramChatId && String(sc.telegramEvents || 'payouts') !== 'none') await telegramSend(sc.telegramChatId, missed, sc.telegramTopicId).catch(() => {}); if (sc.telegramEchoChatId && String(sc.telegramEchoEvents || 'payouts') !== 'none') await telegramSend(sc.telegramEchoChatId, '\u{1F7E0} InstantAdPay \u00b7 ' + missed, sc.telegramEchoTopicId).catch(() => {}); } // The referral follows the money (Marty, 2026-09-16): when a position is created on-chain under a // different sponsor than the account's stored referrer (walk-up past an unactivated sponsor, or the // catch-position fallback), move the account to the sponsor that actually got paid, #1 included. async function sponsorSyncOnEvent(ev) { if (!ev || ev.type !== 'MemberActivated' || !ev.account) return; const acct = await accounts.byAddress(ev.account); if (!acct) return; if (acct.address && acct.address.toLowerCase() !== String(ev.account).toLowerCase()) return; // a linked extra position, not the main wallet const onchain = Number(ev.sponsorId) || 0; if (!onchain) return; const spAcct = await accounts.byMemberId(onchain); if (spAcct && spAcct.email === acct.email) return; // never self-sponsor an account const cur = acct.sponsorRef ? (await resolveSponsorDetailed(acct.sponsorRef)).id : 0; if (cur === onchain) return; const ref = spAcct && spAcct.code ? spAcct.code : String(onchain); const old = acct.sponsorRef || ''; await accounts.setSponsorRef(acct.email, ref); movedByTx.set(ev.tx, { buyerId: Number(ev.id) || 0, toId: onchain, oldRef: old, at: Date.now(), posted: true }); // posted below, at activation; the Purchase branch stays quiet // Post it now (Marty, 2026-09-21): the post used to wait for a Purchase in the same tx, so an activation that // came first (switch on payouts, buy later) was never posted. The activation is the fact; it goes out here. try { const pur = chain.recentEvents(1e9).find(x => x.tx === ev.tx && x.type === 'Purchase') || null; await postWalkUp(Number(ev.id) || 0, old, onchain, pur); } catch (e) { console.error('walk-up post', e.message); } if (movedByTx.size > 200) { const k = movedByTx.keys().next().value; movedByTx.delete(k); } console.log('referral moved to where the pay landed:', acct.email, JSON.stringify(old), '->', ref, '(#' + onchain + ')'); // Notices, now that it is a fact on the chain (Marty, 2026-09-16): everyone between the stored // referrer and the paid sponsor who was simply not activated is told they lost this referral for // good; the sponsor who was paid is told they gained it and should coach the one who missed it. try { const nameOf = a => a.username ? '@' + a.username : a.email.replace(/@.*/, '') + '@'; const toName = spAcct ? nameOf(spAcct) : 'the company (#' + onchain + ')'; const lost = []; const seen = new Set(); let x = await refAccount(old); while (x && !seen.has(x.email) && (!spAcct || x.email !== spAcct.email) && lost.length < 25) { seen.add(x.email); let activated = false; if (x.address) { try { activated = !!(await chain.memberIdByAccount(x.address)); } catch (e) { activated = true; } } if (!activated) lost.push(x); // an activated-but-excluded (no-payout) sponsor is an admin matter, not a "switch on payouts" lesson x = await accounts.sponsorOf(x.email); } for (const l of lost) sponsorHoldNudge(l.email, acct.email, toName).catch(() => {}); if (spAcct && lost.length) sponsorGainNudge(spAcct, nameOf(acct), lost.map(nameOf)).catch(() => {}); } catch (e) { console.error('sponsor move notices', e.message); } } const movedByTx = new Map(); // tx -> { buyerId, toId, oldRef } for purchases routed past an unactivated sponsor async function refAccount(ref) { const t = String(ref || '').trim().toLowerCase(); if (!t) return null; let a = await accounts.byCode(t); if (!a) a = await accounts.byUsername(t); if (!a && /^\d+$/.test(t)) a = await accounts.byMemberId(Number(t)); return a || null; } const sponsorGainLast = new Map(); // sponsor email|buyer -> ts async function sponsorGainNudge(sp, buyerName, lostNames) { if (!sp || !sp.email) return; const key = sp.email + '|' + buyerName; if (sponsorGainLast.has(key)) return; sponsorGainLast.set(key, Date.now()); const who = lostNames.length === 1 ? lostNames[0] : lostNames.slice(0, -1).join(', ') + ' and ' + lostNames[lostNames.length - 1]; const subject = buyerName + ' is now in your line for good (' + who + ' had not switched on payouts)'; const lines = [ buyerName + ' just bought an ad package on InstantAdPay. They came in through ' + who + ', but ' + (lostNames.length === 1 ? who + ' had' : 'they had') + ' not linked a wallet and switched on payouts, so the contract could not pay ' + (lostNames.length === 1 ? 'them' : 'any of them') + '. It walked up your line and paid you at level 1 instead.', 'That is permanent. The contract locks a buyer to the sponsor who was paid at their first purchase, so ' + buyerName + ' is in your line on the chain from now on: this purchase and every one after it pays you as their level 1. They now show under you in My line.', 'The lesson to pass down: ' + who + ' just lost a referral they worked for because payouts were off. Take two minutes with them, help them link a wallet and switch on payouts (Wallet tab, one free signature and one small transaction), and show them how to check it. Every leader in this line who teaches that step keeps their people from losing the next one.', 'My line: https://instantadpay.com/my#line' ]; const text = lines.join('\n\n'); if (mailer.hasKey()) mailer.send(sp.email, subject, text + '\n\nInstantAdPay').catch(() => {}); try { const html = lines.map(l => '

' + l.replace(/&/g, '&').replace(/$1') + '

').join(''); await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [sp.email], subject, html, 'notice'); } catch (e) {} } // Partner-code credits arrive as a drip (Marty, 2026-09-19): a fifth on the day the code is // redeemed, then a fifth on each day the member finishes their daily ad set, five days in all, // nothing after 30 days. Same headline number for the partner to promote; paid only to people // who show up. Member-funded codes charge the funder one installment at a time. async function payPromo(email, day) { return promos.payDue(email, day, async (r, amount, n) => { if (r.funder && r.funder !== email) { const ok = await ads.spendEarned(r.funder, amount, { log: { kind: 'promo', note: 'Partner code ' + r.code + ' funded for ' + email + ' (day ' + n + ' of ' + promos.STEPS + ')' } }); if (!ok) { console.log('promo funder short', r.code, r.funder); return false; } } await ads.addEarned(email, amount, { log: { kind: 'promo', note: 'Partner code ' + r.code + ': ' + amount + ' of ' + r.credits + ' (day ' + n + ' of ' + promos.STEPS + ')' } }); return true; }); } // anti-fraud admin alert (Telegram admin chat, else email): who, which flags, and whether the sign-up was blocked function fraudAlert(email, fc, spAcct, blocked) { try { const sc = siteConfig(); const text = (blocked ? '\u26D4 InstantAdPay sign-up BLOCKED: ' : '\u{1F6A9} InstantAdPay sign-up flagged: ') + String(email).replace(/^(.{2}).*(@.*)$/, '$1***$2') + ' [' + (fc.flags || []).join(', ') + ']' + (fc.ip ? ' ip ' + fc.ip : '') + (spAcct ? ' sponsor ' + (spAcct.username ? '@' + spAcct.username : spAcct.email) : '') + '. Admin > Members > Duplicate signals.'; if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: sign-up ' + (blocked ? 'blocked' : 'flagged'), text).catch(() => {}); } catch (e) {} } const sponsorRoutedLast = new Map(); // buyer email -> ts (one alert per buyer per hour) function sponsorRoutedAlert(who, spd, routed, skipped) { const k = String(who || '?'); if (Date.now() - (sponsorRoutedLast.get(k) || 0) < 3600000) return; sponsorRoutedLast.set(k, Date.now()); const text = '\u2934\uFE0F InstantAdPay heads-up: ' + k.replace(/^(.{2}).*(@.*)$/, '$1***$2') + ' is at checkout. Their sponsor ' + (spd.name || '?') + ' has not switched on payouts, so if this purchase goes through it pays ' + routed.to + ' instead.' + (skipped.length > 1 ? ' Also skipped: ' + skipped.map(x => x.name).join(', ') + '.' : '') + ' Nothing has been paid yet; the on-chain result posts to Telegram when the sale settles.'; const sc = siteConfig(); if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay heads-up: a checkout will route past an unactivated sponsor', text).catch(() => {}); // the skipped sponsor and the one who gains are told by sponsorSyncOnEvent, once the position exists on the chain } const sponsorAlertLast = new Map(); // email -> ts (one alert per member per hour) function sponsorBlockedAlert(who, r) { const k = String(who || '?'); if (Date.now() - (sponsorAlertLast.get(k) || 0) < 3600000) return; sponsorAlertLast.set(k, Date.now()); const text = '\u26A0\uFE0F InstantAdPay: purchase held for ' + k.replace(/^(.{2}).*(@.*)$/, '$1***$2') + '. Their sponsor ' + (r.name || r.tok || '?') + ' could not be resolved (' + r.reason + '), so the buy was blocked instead of crediting the company. ' + (r.reason === 'notActivated' ? 'The sponsor needs to switch on payouts.' : r.reason === 'rpc' ? 'Chain lookup failed; they can retry.' : 'Check the sponsor field in Admin > Members.'); const sc = siteConfig(); if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: purchase held, sponsor unresolved', text).catch(() => {}); // and the sponsor themselves (Marty, 2026-09-15): a buy is waiting on them, once an hour at most if (r.reason === 'notActivated') sponsorHoldNudge(String(r.tok || ''), k).catch(() => {}); } const sponsorNudgeLast = new Map(); // sponsor email -> ts async function sponsorHoldNudge(tok, buyerEmail, routedTo) { const t = String(tok || '').trim().toLowerCase(); if (!t) return; let sp = t.includes('@') ? await accounts.byEmail(t) : null; if (!sp) sp = await accounts.byCode(t); if (!sp) sp = await accounts.byUsername(t); if (!sp || !sp.email) return; if (Date.now() - (sponsorNudgeLast.get(sp.email) || 0) < 3600000) return; sponsorNudgeLast.set(sp.email, Date.now()); const buyer = await accounts.byEmail(buyerEmail); const bn = buyer && buyer.username ? '@' + buyer.username : 'One of your referrals'; const step = !sp.address ? 'link a wallet and switch on payouts' : 'switch on payouts'; const subject = routedTo ? 'You lost ' + bn + ' to ' + routedTo + ' (payouts were not switched on)' : bn + ' is trying to buy. ' + (sp.address ? 'Switch on payouts' : 'Link your wallet') + ' so it pays you'; const lines = routedTo ? [ 'You just lost a sale. ' + bn + ' is buying an ad package on InstantAdPay right now, and because payouts are not switched on for your account, the contract cannot pay you. The purchase is paying ' + routedTo + ' instead.', 'This is permanent. The contract locks a buyer to the sponsor who was paid at their first purchase, so ' + bn + ' now belongs to ' + routedTo + ', has moved out of your line, and will never pay you from that wallet. Nothing can bring that referral back.', 'The next one does not have to go the same way. Open the Wallet tab and ' + step + '. One free signature to link, one small transaction to switch on. Two minutes, and every purchase in your line from then on pays you the moment it happens.', 'Wallet tab: https://instantadpay.com/my#wallet' ] : [ bn + ' just tried to buy an ad package on InstantAdPay, and the purchase is on hold because payouts are not switched on for your account yet. The contract pays your share in the same transaction, but only to a wallet that is switched on, so the site paused the buy instead of sending your 50 percent to someone else.', 'To fix it: open the Wallet tab and ' + step + '. One free signature to link, one small transaction to switch on. Takes two minutes, and every purchase in your line from then on pays you the moment it happens.', 'Until then their purchase waits, and they have been told why.', 'Wallet tab: https://instantadpay.com/my#wallet' ]; const text = lines.join('\n\n'); if (mailer.hasKey()) mailer.send(sp.email, subject, text + '\n\nInstantAdPay').catch(() => {}); try { const html = lines.map(l => '

' + l.replace(/&/g, '&').replace(/$1') + '

').join(''); await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [sp.email], subject, html, 'alert'); } catch (e) {} } // The moment someone joins through a code, nudge its owner to activate. // Email a member's sponsor the moment they get a new referral (free OR paid). // Resolves the sponsor from the join token by member id, share code, or username, // and notifies EVERY sponsor โ€” activated or not (an active sponsor still wants to // know their team grew). A referral is on the line from signup; it only counts // toward qualification once it makes a $20+ purchase. async function notifyNewReferral(ref, newAcct) { try { if (!mailer.hasKey()) return; const t = String(ref || '').trim().toLowerCase(); if (!t) return; let owner = null; if (/^\d+$/.test(t)) { try { owner = await accounts.byMemberId(Number(t)); } catch (e) {} } if (!owner) { try { owner = await accounts.byCode(t); } catch (e) {} } if (!owner) { try { owner = await accounts.byUsername(t); } catch (e) {} } if (!owner || !owner.email) return; const who = newAcct && newAcct.username ? '@' + newAcct.username : 'A new member'; let body = who + ' just joined InstantAdPay through your link โ€” they are on your team from today.\n\n' + 'They count toward your qualification once they make a $20+ purchase.\n\n'; if (!owner.address) body += 'Make sure payouts are switched on (one free wallet step) so you never miss a commission โ€” ' + 'the contract locks each buyer to their sponsor at their first purchase.\n\n'; body += 'See your team: https://instantadpay.com/my\n\nInstantAdPay'; mailer.send(owner.email, 'You have a new referral on InstantAdPay', body) .catch(e => console.error('referral notify failed', e.message)); } catch (e) {} } // welcome email on a new account: onboarding steps + who their sponsor is // how a member is named in emails/alerts: @username, a linked position named after its owner, else member #id async function memberLabel(id) { try { let a = await accounts.byMemberId(id); if (!a) { // brand-new buyer: the account learns its member id on its next page load, so read the // wallet from the chain and match it now (Jim's purchase mailed as "member #25", 2026-09-13) try { const m = await chain.member(id); if (m && m.account) { a = await accounts.byAddress(String(m.account).toLowerCase()); if (a && !a.memberId) accounts.setMemberId(a.email, id).catch(() => {}); } } catch (e) {} } if (a && a.username) return '@' + a.username; const pos = await accounts.positionByMember(id); if (pos && pos.email) { const o = await accounts.byEmail(pos.email); if (o && o.username) return '@' + o.username + ' (extra position #' + id + ')'; } } catch (e) {} return 'member #' + id; } async function sendWelcome(email, ref) { try { if (!mailer.hasKey()) return; const spon = await accounts.sponsorOf(email); const who = spon ? (spon.username ? '@' + spon.username : 'member #' + (spon.memberId || 0)) : ''; const sponsorLine = who ? ('You joined through ' + who + ', your sponsor. They are there to help you get started, and you can message them anytime from your dashboard.\n\n') : ''; mailer.send(email, 'Welcome to InstantAdPay', 'Your free InstantAdPay account is ready.\n\n' + sponsorLine + 'Getting started:\n' + '1. Pick your username and fill out your profile.\n' + '2. Grab your invite link and start sharing to build your line.\n' + '3. Explore the ad packages when you are ready. Every payout settles on-chain, straight to your wallet.\n\n' + 'Sign in anytime: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {}); } catch (e) {} } // on-chain event emails: a payout received, or a payout that passed you by const weiToPol = w => { try { return (Number(BigInt(w) / (10n ** 14n)) / 10000).toString(); } catch (e) { return '?'; } }; async function emailOnEvent(ev) { if (!ev) return; const notify = async (memberId, subject, body) => { if (!memberId || !mailer.hasKey()) return; const a = await accounts.byMemberId(memberId); if (a && a.email) mailer.send(a.email, subject, body + '\n\nSee it on the live ledger: https://instantadpay.com/ledger\n\nInstantAdPay').catch(() => {}); }; // Marty (2026-09-15): payment and missed-payment notices also land in the on-site inbox, so // they are waiting (login modal + Messages card) whether or not the email was read. Sent as // the company account (#1), the same sender the credit-return notes used. Plain text in, HTML out. const inboxNote = async (memberId, subject, text, kind) => { if (!memberId) return; try { const a = await accounts.byMemberId(memberId); if (!a || !a.email) return; const html = '

' + String(text).replace(/&/g, '&').replace(//g, '>') .replace(/(https:\/\/[^\s]+)/g, '$1').split('\n\n').join('

').replace(/\n/g, '
') + '

'; await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [a.email], subject, html, kind || 'notice'); } catch (e) {} }; const tell = async (memberId, subject, text, kind) => { await notify(memberId, subject, text); await inboxNote(memberId, subject, text, kind); }; const PCT = { 1: 50, 2: 20, 3: 10 }; // the Purchase event of the same tx is already indexed (it precedes every payout log), so the // dollar side of any share is that purchase's price times the tier percentage const purchaseOf = tx => { try { return chain.recentEvents(1e9).find(e => e.tx === tx && e.type === 'Purchase'); } catch (e) { return null; } }; const usdShare = (pur, pct) => pur ? (' (about ' + ('$' + (pur.priceCents * pct / 10000).toFixed(2)).replace(/\.00$/, '') + ')') : ''; const txUrlOf = tx => { const cc = chain.getConfig(); return (cc.explorer ? cc.explorer.replace(/\/+$/, '') : 'https://polygonscan.com') + '/tx/' + tx; }; if (ev.type === 'Purchase') { const cc = chain.getConfig(); const txUrl = (cc.explorer ? cc.explorer.replace(/\/+$/, '') : 'https://polygonscan.com') + '/tx/' + ev.tx; let bal = ev.creditAmount; try { bal = await chain.creditBalance(ev.buyerId, ev.creditType); } catch (e) {} await notify(ev.buyerId, 'Your InstantAdPay purchase is confirmed', 'Your purchase is complete and settled on-chain.\n\n' + 'Ad credits added: ' + ev.creditAmount + '\n' + 'Your ad-credit balance is now: ' + bal + '\n' + 'Amount paid: ' + weiToPol(ev.paidWei) + ' POL\n\n' + 'View your transaction on the blockchain:\n' + txUrl); // tell the buyer's DIRECT sponsor their referral just bought (upline earners // are separately notified by the TierPaid payout email when they earn) try { const buyer = await chain.member(ev.buyerId); if (buyer && buyer.sponsorId) { const sp = await accounts.byMemberId(buyer.sponsorId); if (sp && sp.email) { const bn = await memberLabel(ev.buyerId); // @username at a glance (Marty, 2026-09-12) const usd = ('$' + (ev.priceCents / 100).toFixed(2)).replace(/\.00$/, ''); const qual = ev.priceCents >= 2000 ? ' This is a $20+ purchase, so it counts toward your qualification.' : ' (Purchases under $20 do not count toward qualification.)'; mailer.send(sp.email, bn + ' just bought a ' + usd + ' ad package', 'Your referral ' + bn + ' (member #' + ev.buyerId + ') just purchased a package (' + usd + ' โ€” ' + ev.creditAmount + ' credits).' + qual + '\n\n' + 'See your team and the live ledger: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {}); } } } catch (e) {} } else if (ev.type === 'TierPaid') { const pct = PCT[ev.tier] || 0; const pur = purchaseOf(ev.tx); let buyer = 'a member on your level ' + ev.tier; try { buyer = await memberLabel(ev.buyerId); } catch (e) {} const pol = weiToPol(ev.amountWei); await tell(ev.recipientId, 'You just got paid ' + pol + ' POL on InstantAdPay', buyer + ' just bought an ad package on your level ' + ev.tier + (pur ? ' ($' + (pur.priceCents / 100).toFixed(2).replace(/\.00$/, '') + ')' : '') + '. Your ' + pct + ' percent share, ' + pol + ' POL' + usdShare(pur, pct) + ', landed in your wallet in the same transaction' + (ev.hops ? ', passed up to you because someone between you was not qualified for this level' : '') + '.\n\n' + 'Transaction: ' + txUrlOf(ev.tx) + '\n\nYour dashboard: https://instantadpay.com/my'); } else if (ev.type === 'AwardPaid') await tell(ev.toId, 'You just got paid ' + weiToPol(ev.amountWei) + ' POL on InstantAdPay', weiToPol(ev.amountWei) + ' POL just landed in your wallet.\n\nTransaction: ' + txUrlOf(ev.tx) + '\n\nYour dashboard: https://instantadpay.com/my'); else if (ev.type === 'PassedUp' && ev.reason === 'send-failed') { // the member WAS qualified but their wallet rejected the POL (usually a smart-contract // wallet that needs more than the capped gas): tell them and the admin, loudly await tell(ev.skippedId, 'Your wallet rejected a payout on InstantAdPay', 'A level-' + ev.tier + ' payout tried to reach your linked wallet and the wallet refused the transfer, so it passed to the next qualified member. This happens with some smart-contract wallets. Link a regular wallet address (MetaMask, SafePal, Phantom) on the Wallet tab so the next payout lands.\n\nTransaction: ' + txUrlOf(ev.tx)); if (ADMIN_EMAIL) mailer.send(ADMIN_EMAIL, 'InstantAdPay: payout send-failed for member #' + ev.skippedId, 'A level-' + ev.tier + ' payout to member #' + ev.skippedId + ' failed at the wallet (send-failed) and passed up. Tx: ' + ev.tx + '\n\nThe member has been emailed to link a regular wallet.').catch(() => {}); } else if (ev.type === 'PassedUp') { // Marty (2026-09-15): a member who missed a payout because they were not qualified must be told // exactly what they missed: who bought, the share in POL and dollars, and how to close the gap. const pct = PCT[ev.tier] || 0; const pur = purchaseOf(ev.tx); let pol = '', buyer = 'a member on your level ' + ev.tier; try { if (pur) pol = weiToPol((BigInt(pur.paidWei) * BigInt(pct) / 100n).toString()); buyer = await memberLabel(ev.buyerId); } catch (e) {} let bc = 0; try { bc = (await chain.member(ev.skippedId)).buyerCount || 0; } catch (e) {} const need = ev.tier === 3 ? 5 : 2, short = Math.max(0, need - bc); const what = pol ? pol + ' POL' + usdShare(pur, pct) : 'a level-' + ev.tier + ' payout'; await tell(ev.skippedId, 'You missed ' + (pol ? pol + ' POL' : 'a payout') + ' on InstantAdPay', buyer + ' just bought an ad package on your level ' + ev.tier + '. Your share was ' + what + ', and it passed you by because level ' + ev.tier + ' is not open on your account yet. The contract paid it to the next qualified person above you.\n\n' + 'Level ' + ev.tier + ' opens at ' + need + ' qualifying buyers (people you referred who bought a $20 or larger package). You have ' + bc + (short ? ', so you are ' + short + ' buyer' + (short === 1 ? '' : 's') + ' away.' : '.') + '\n\n' + 'Two ways to close the gap: bring ' + (short || 1) + ' more buyer' + (short === 1 ? '' : 's') + ' from your My line page, or use Qualified Start under Buy packages to be your own buyer today. Every package on level ' + ev.tier + ' pays you ' + pct + ' percent once it is open, and the next one is coming whether you are ready or not.\n\n' + 'Transaction: ' + txUrlOf(ev.tx) + '\n\nYour dashboard: https://instantadpay.com/my', 'alert'); } } // payment-proof Telegram feed (same pattern as the RM Circle proof channel): one compact // line per event, admin-configured under Settings > Site (telegramBotToken, telegramChatId, // optional telegramTopicId, telegramEvents = payouts | payouts+purchases | all, telegramCtaUrl) // A second target, telegramEchoChatId + telegramEchoTopicId (+ telegramEchoEvents), echoes // the same lines into a shared cross-program payments topic that RM Circle also posts to, // so every line there carries the program name. async function telegramOnEvent(ev) { const sc = siteConfig(); if (!sc.telegramBotToken || !ev) return; if (!sc.telegramChatId && !sc.telegramEchoChatId) return; const names = await accounts.namesForMembers([ev.recipientId, ev.buyerId, ev.sponsorId, ev.newBuyerId, ev.id].filter(Boolean)).catch(() => ({})); const who = id => '#' + id + (names[id] ? ' @' + names[id] : ''); const cc = chain.getConfig(); const tx = (cc.explorer ? cc.explorer.replace(/\/+$/, '') : 'https://polygonscan.com') + '/tx/' + ev.tx; const build = (mode) => { let line = null; if (ev.type === 'TierPaid') line = '\u{1F4B8} Level ' + ev.tier + ' payout: ' + weiToPol(ev.amountWei) + ' POL \u2192 ' + who(ev.recipientId); else if (ev.type === 'BuyerCounted' && mode !== 'payouts') line = '\u2B50 ' + who(ev.sponsorId) + ' now has ' + ev.newCount + ' qualifying buyer' + (ev.newCount === 1 ? '' : 's') + (ev.newCount === 2 ? ' \u00b7 level 2 open' : ev.newCount === 5 ? ' \u00b7 level 3 open' : ''); else if (ev.type === 'Purchase' && mode !== 'payouts') line = '\u{1F9FE} ' + who(ev.buyerId) + ' bought a $' + Math.round(ev.priceCents / 100) + ' package'; else if (ev.type === 'AwardPaid') line = '\u{1F4B8} Award payout: ' + weiToPol(ev.amountWei) + ' POL \u2192 ' + who(ev.toId); else if (ev.type === 'MemberActivated' && mode === 'all') line = '\u{1F91D} ' + who(ev.id) + ' switched on payouts'; if (!line) return null; return line + ' \u00b7 verify' + (sc.telegramCtaUrl ? '\nJoin free' : ''); }; // Missed-payout lines (Marty, 2026-09-16): when a share is passed over, say who missed it and who got it. // (a) on-chain pass-up: the TierPaid with hops>0 follows the PassedUp events of the same tx, already indexed. // (b) off-chain walk-up past an unactivated sponsor: recorded by sponsorSyncOnEvent, posted on the Purchase. let missed = null; try { const site = 'https://instantadpay.com/my'; // (a) on-chain pass-ups: AdminPaid is the last event of every purchase tx, so by then every // PassedUp and TierPaid of the tx is indexed; one message shows the whole chain (missed.js). if (ev.type === 'AdminPaid') { const all = chain.recentEvents(1e9); const txEvents = all.filter(x => x.tx === ev.tx); if (txEvents.some(x => x.type === 'PassedUp')) { const ids = new Set(); txEvents.forEach(x => { for (const k of ['buyerId', 'skippedId', 'recipientId']) if (x[k]) ids.add(x[k]); }); const nm2 = await accounts.namesForMembers([...ids]).catch(() => ({})); const who2 = id => '#' + id + (nm2[id] ? ' @' + nm2[id] : ''); const had = (id, tier) => all.filter(y => y.type === 'BuyerCounted' && y.sponsorId === id && y.block < ev.block).length; missed = missedNotice.composeMissed(txEvents, who2, had, site); } } if (ev.type === 'Purchase' && movedByTx.has(ev.tx) && !movedByTx.get(ev.tx).posted) { // legacy path; walk-ups now post at activation const mv = movedByTx.get(ev.tx); movedByTx.delete(ev.tx); const lostAcct = await refAccount(mv.oldRef); if (lostAcct) { const lostName = lostAcct.username ? '@' + lostAcct.username : lostAcct.email.replace(/@.*/, '') + '@'; const nm3 = await accounts.namesForMembers([mv.toId]).catch(() => ({})); missed = '\u{1F62C} Missed sale: ' + lostName + ' had not switched on payouts, so ' + who(ev.buyerId) + "'s $" + Math.round(ev.priceCents / 100) + ' purchase routed past them. That buyer, and every payout from them, now belongs to #' + mv.toId + (nm3[mv.toId] ? ' @' + nm3[mv.toId] : '') + ' for good.' + '\nTwo minutes on the Wallet tab prevents this: ' + site.replace(/^https:\/\//, '') + ''; } } } catch (e) { console.error('missed-payout post', e.message); } if (sc.telegramChatId) { const t = build(String(sc.telegramEvents || 'payouts')); if (t) await telegramSend(sc.telegramChatId, t, sc.telegramTopicId); if (missed && String(sc.telegramEvents || 'payouts') !== 'none') await telegramSend(sc.telegramChatId, missed, sc.telegramTopicId).catch(() => {}); } if (sc.telegramEchoChatId) { const t = build(String(sc.telegramEchoEvents || 'payouts')); if (t) await telegramSend(sc.telegramEchoChatId, '\u{1F7E0} InstantAdPay \u00b7 ' + t, sc.telegramEchoTopicId); } if (missed && sc.telegramEchoChatId && String(sc.telegramEchoEvents || 'payouts') !== 'none') await telegramSend(sc.telegramEchoChatId, '\u{1F7E0} InstantAdPay \u00b7 ' + missed, sc.telegramEchoTopicId).catch(() => {}); } // holding-tank arrivals -> one digest line in the shared payments topic (Marty, 2026-09-12): who is // waiting for a sponsor, by username, so builders go adopt them. Runs every 15 min, posts only // when someone new landed since the last check. async function tankNotifyTick() { const sc = siteConfig(); if (!sc.telegramBotToken || !sc.telegramEchoChatId) return; const f = path.join(DATA_DIR, 'tank-notify.json'); let st = { last: 0 }; try { st = JSON.parse(fs.readFileSync(f, 'utf8')); } catch (e) {} const since = st.last || (Date.now() - 24 * 3600 * 1000); const fresh = (await tank.waiting()).filter(w => (w.joined || 0) > since); st.last = Date.now(); fs.writeFileSync(f, JSON.stringify(st)); if (!fresh.length) return; const named = fresh.filter(w => w.username).map(w => '@' + w.username); const who = named.length ? ': ' + named.slice(0, 8).join(', ') + (named.length > 8 ? ' and ' + (named.length - 8) + ' more' : '') : ''; const text = '\u{1FAA3} InstantAdPay \u00b7 ' + fresh.length + ' new member' + (fresh.length === 1 ? '' : 's') + ' waiting for a sponsor in the holding tank' + who + '\nAdopt from My line \u203a Holding tank: instantadpay.com/my'; await telegramSend(sc.telegramEchoChatId, text, sc.telegramEchoTopicId); } // one sendMessage call; never throws, never logs the token async function telegramSend(chatId, text, threadId) { const sc = siteConfig(); if (!sc.telegramBotToken || !chatId) return; const body = JSON.stringify(Object.assign({ chat_id: chatId, text, parse_mode: 'HTML', disable_web_page_preview: true }, threadId ? { message_thread_id: Number(threadId) } : {})); await new Promise((resolve) => { const rq = https.request({ hostname: 'api.telegram.org', path: '/bot' + sc.telegramBotToken + '/sendMessage', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 10000 }, r => { r.resume(); r.on('end', resolve); }); rq.on('error', () => resolve()); rq.on('timeout', () => { rq.destroy(); resolve(); }); rq.end(body); }); } // photo post (multipart) for the achievement badges (Marty, 2026-09-13); same bot, sendPhoto only async function telegramSendPhoto(chatId, jpeg, caption, threadId) { const sc = siteConfig(); if (!sc.telegramBotToken || !chatId || !jpeg) return false; const B = '----iapbadge' + crypto.randomBytes(8).toString('hex'); const field = (n, v) => Buffer.from('--' + B + '\r\nContent-Disposition: form-data; name="' + n + '"\r\n\r\n' + v + '\r\n'); const parts = [field('chat_id', String(chatId)), field('caption', caption), field('parse_mode', 'HTML')]; if (threadId) parts.push(field('message_thread_id', String(Number(threadId)))); parts.push(Buffer.from('--' + B + '\r\nContent-Disposition: form-data; name="photo"; filename="badge.jpg"\r\nContent-Type: image/jpeg\r\n\r\n'), jpeg, Buffer.from('\r\n--' + B + '--\r\n')); const body = Buffer.concat(parts); return new Promise((resolve) => { const rq = https.request({ hostname: 'api.telegram.org', path: '/bot' + sc.telegramBotToken + '/sendPhoto', method: 'POST', headers: { 'Content-Type': 'multipart/form-data; boundary=' + B, 'Content-Length': body.length }, timeout: 20000 }, res => { let out = ''; res.on('data', c => out += c); res.on('end', () => resolve(res.statusCode === 200)); }); rq.on('error', () => resolve(false)); rq.on('timeout', () => { rq.destroy(); resolve(false); }); rq.end(body); }); } const BADGE_META = { payouts: ['Spark', 'payouts switched on'], firstBuyer: ['Surge', 'first qualifying buyer'], level2: ['Circuit', 'two qualifying buyers, level 2 open'], level3: ['Nexus', 'five qualifying buyers, fully qualified'], fridays: ['Five Fridays', 'five Five Dollar Fridays in a row'] }; const BADGE_LOG = () => path.join(DATA_DIR, 'badge-posts.json'); function badgeLog() { try { return JSON.parse(fs.readFileSync(BADGE_LOG(), 'utf8')); } catch (e) { return {}; } } // ---- live feed (SSE) ---- const feedClients = new Set(); function pushFeed(ev) { const line = 'data: ' + JSON.stringify(ev) + '\n\n'; for (const res of feedClients) { try { res.write(line); } catch (e) { feedClients.delete(res); } } } // ---- server ---- const server = http.createServer(async (req, res) => { try { const u = new URL(req.url, 'http://x'); const p = u.pathname; // -- traffic log: public page views by referring domain (admin > Traffic) if (req.method === 'GET' && (TRAFFIC_PAGES.has(p) || /^\/(join|from|wall|blog)\/[^/]+$/.test(p))) traffic.hit(p.startsWith('/blog/') ? '/blog/*' : p, req.headers.referer, req.headers['user-agent']); // -- viral links (Marty, 2026-09-21): ANY public page + ?ref= is that member's // referral link. Same 30-day last-touch sponsor cookie as /join, the view counts under the // "page" hook in link stats. Served IN PLACE, not redirected (2026-09-21): Facebook's crawler follows // redirects and canonicalizes a share to og:url, so the redirect (and a clean og:url) dropped the member's // name from every share. og:url now carries the ref; rel=canonical stays clean for search engines. // /join, /from and the APIs keep their own meaning of ?ref. if ((req.method === 'GET' || req.method === 'HEAD') && u.searchParams.has('ref') && !p.startsWith('/api/') && !p.startsWith('/admin') && !/^\/(join|from)\//.test(p) && !/\.[a-z0-9]{2,5}$/i.test(p)) { const raw = String(u.searchParams.get('ref') || '').trim().toLowerCase().slice(0, 40); const tok = JOIN_ALIASES[raw] || raw; u.searchParams.delete('ref'); const clean = p + (u.searchParams.toString() ? '?' + u.searchParams.toString() : ''); let known = false; if (/^[a-z0-9_]{1,20}$/.test(tok)) { try { known = !!((await accounts.byCode(tok)) || (await accounts.byUsername(tok)) || (/^\d+$/.test(tok) && (await accounts.byMemberId(Number(tok))))); } catch (e) {} } const set = []; if (known) { const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${30 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`; set.push('iap.sponsor=' + tok + cookieTail); // last touch wins, exactly like /join set.push('iap.angle=page' + cookieTail); // the join shows under the "any page" hook if (!parseCookies(req)['iap.ref']) set.push('iap.ref=' + encodeURIComponent(coach.refHost(req.headers.referer)) + cookieTail); // first-touch source if (req.method === 'GET') coach.recordView(tok, 'page', req.headers.referer); } if (known) { const wh = res.writeHead.bind(res); let html = false; res.writeHead = function (code, headers) { headers = Object.assign({}, headers || {}); if (set.length) headers['Set-Cookie'] = [].concat(headers['Set-Cookie'] || [], set); html = /text\/html/i.test(String(headers['Content-Type'] || '')); if (html) delete headers['Content-Length']; return wh(code, headers); }; const end = res.end.bind(res); res.end = function (chunk, enc, cb) { if (html && chunk) { let s = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk); s = s.replace(/( a + url + (url.includes('?') ? '&' : '?') + 'ref=' + encodeURIComponent(tok) + c); return end(s, 'utf8', cb); } return end(chunk, enc, cb); }; } req.url = clean; // the routes below see the clean path; u already has the ref removed } // -- join links: /join/ โ€” LAST-touch cookie (Marty, // 2026-09-10): the link a visitor opened most recently is the sponsor shown // and used, and it locks the moment the account is created (accounts.ensure // never changes an existing account's sponsor; the contract binds the buyer at // their first purchase). Codes resolve LATE (at buy time) to whatever chain id // the referrer has by then, so free members refer from day one. let m = /^\/join\/([A-Za-z0-9_]{1,20})$/.exec(p); if (m && (req.method === 'GET' || req.method === 'HEAD')) { // lead-capture page: email first, wallet later. ?v= picks the hook // copy and is remembered so the account records which angle converted. // partner placement: instantadpay.com/join/company (or /top) places the visitor directly under // member #1, the company position, with no sponsor in between (Marty, 2026-09-12 partner kit) const tok = JOIN_ALIASES[m[1].toLowerCase()] || m[1].toLowerCase(); const cookies = parseCookies(req); const angle = String(u.searchParams.get('v') || '').toLowerCase(); const ang = JOIN_ANGLES[angle] || null; if (req.method === 'GET') coach.recordView(tok, ang ? angle : '', req.headers.referer); // link stats per angle + source const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${30 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`; // 30 days: whoever brings them back gets the credit const set = []; set.push('iap.sponsor=' + tok + cookieTail); // last touch wins if (u.searchParams.get('from') === 'polhunter') set.push('iap.return=polhunter' + cookieTail); // came through a PolHunter share link: send them back after the wallet step const promo = promos.norm(u.searchParams.get('promo')); if (promo) set.push('iap.promo=' + promo + cookieTail); // partner code, redeemed at signup if (promo) { const pref = String(req.headers.referer || '').replace(/^https?:\/\//, '').split('/')[0].toLowerCase().slice(0, 80); if (pref) set.push('iap.promoref=' + encodeURIComponent(pref) + cookieTail); } // which partner page it came from if (ang) set.push('iap.angle=' + angle + cookieTail); // First-touch source. A PolHunter share link carries ?from=polhunter in the URL itself, which // survives the referrer being stripped (in-app browsers, a link pasted into chat). PolHunter // pays a referral bounty only for people it actually sent, so that proof has to be reliable // rather than dependent on a header (Marty, 2026-09-24: "not just members that come on board // IAP and work their way over to POL Hunter"). if (!cookies['iap.ref']) { const host = u.searchParams.get('from') === 'polhunter' ? 'polhunter.com' : coach.refHost(req.headers.referer); set.push('iap.ref=' + encodeURIComponent(host) + cookieTail); } return serveJoinPage(res, tok, ang ? angle : '', ang, set); } // -- legacy bridge: /from/faucetwave | /from/tieroneads [?seg=advertiser]. Same squeeze page // with brand copy, NO sponsor (the sponsor cookie is cleared so they land in the holding // tank for adoption), the angle remembered so the welcome-back grant fires at signup, and // a forced first-touch source so link stats / admin can see the legacy arrivals. m = /^\/from\/(faucetwave|tieroneads|socialpix)$/.exec(p); if (m && (req.method === 'GET' || req.method === 'HEAD')) { const brand = m[1]; // SocialPix arrivals came to POST ads, so their default copy is the advertiser one (?seg=earn flips it); // the closed EvolutionScript brands default to the earner copy (?seg=advertiser flips it). const seg = String(u.searchParams.get('seg') || '').toLowerCase(); const prefix = { faucetwave: 'fw', tieroneads: 't1', socialpix: 'sp' }[brand]; const key = prefix + (brand === 'socialpix' ? (seg.startsWith('earn') ? '-earn' : '-adv') : (seg.startsWith('adv') ? '-adv' : '-earn')); const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${30 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`; const src = String(u.searchParams.get('src') || '').toLowerCase().replace(/[^a-z0-9.\-]/g, '').slice(0, 40); // e.g. the old domain's splash page const set = ['iap.sponsor=; Path=/; SameSite=Lax; Max-Age=0' + (IS_PROD ? '; Secure' : ''), 'iap.angle=' + key + cookieTail, 'iap.ref=' + encodeURIComponent('legacy:' + brand + (src ? ':' + src : '')) + cookieTail]; return serveJoinPage(res, '', key, JOIN_ANGLES[key], set); } if (p === '/unsubscribe' && req.method === 'GET') { const r = await drip.unsubscribe(u.searchParams.get('e'), u.searchParams.get('t')); const msg = r.error ? r.error : 'Done. You will not get any more follow-up emails from InstantAdPay. Your account is unchanged.'; res.writeHead(r.error ? 400 : 200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' })); return res.end('InstantAdPay
InstantAdPay

' + (r.error ? 'Hmm.' : 'Unsubscribed.') + '

' + msg + '

Member area

'); } // -- public API if (p === '/api/friday' && req.method === 'GET') return json(res, 200, friday.view(), { 'Cache-Control': 'public, max-age=60' }); // -- the pages a member can turn into a viral link (promo tools > Viral links) if (p === '/api/pages' && req.method === 'GET') { const pages = [{ path: '/', title: 'Home page' }, { path: '/blog', title: 'Blog' }, { path: '/earning', title: 'How earning works' }, { path: '/ledger', title: 'Live ledger' }, { path: '/leaderboard', title: 'Leaderboard' }, { path: '/contract', title: 'The contract' }, { path: '/plays', title: 'Team-building plays' }, { path: '/wallets', title: 'Wallet guide' }, { path: '/whats-new', title: "What's new" }, { path: '/shorts', title: 'Shorts' }]; try { for (const b of (await blog.listPublished()).slice(0, 40)) pages.push({ path: '/blog/' + b.slug, title: 'Article: ' + b.title, cover: b.cover || null }); } catch (e) {} return json(res, 200, { pages }); } if (p === '/api/config' && req.method === 'GET') { const c = chain.getConfig(); // public copy of the site settings: never anything that looks like a credential const pubSite = {}; for (const [k, v] of Object.entries(siteConfig())) if (!/secret|token|password|private|apikey|api_key/i.test(k)) pubSite[k] = v; return json(res, 200, Object.assign({ contract: c.contract, chainId: c.chainId, chainName: c.chainName, explorer: c.explorer, rpc: c.rpcs[0], emailAuth: mailer.hasKey() || !IS_PROD }, pubSite)); } if (p === '/api/moonpay-url' && req.method === 'GET') { // Card on-ramp deep link. With MoonPay keys set โ€” PUBLIC key via // MOONPAY_PUBLIC_KEY env or site config, SECRET key via MOONPAY_SECRET_KEY // env ONLY (never site config, since /api/config exposes siteConfig) โ€” // returns a SIGNED checkout URL prefilled with the buyer's own wallet and // a POL amount; otherwise a generic MoonPay buy page. Zero custody either // way: MoonPay is merchant of record and the crypto goes straight to the // buyer's wallet โ€” this site never touches or holds anyone's money. const addr = (u.searchParams.get('address') || '').trim(); let pol = Math.round(Number(u.searchParams.get('pol')) || 0); if (!pol || pol < 30) pol = 30; if (pol > 100000) pol = 100000; const pk = (process.env.MOONPAY_PUBLIC_KEY || siteConfig().moonpayPublicKey || '').trim(); const sk = (process.env.MOONPAY_SECRET_KEY || '').trim(); if (pk && sk && /^0x[0-9a-fA-F]{40}$/.test(addr)) { const qs = '?apiKey=' + encodeURIComponent(pk) + '¤cyCode=pol_polygon&walletAddress=' + encodeURIComponent(addr) + '"eCurrencyAmount=' + pol; const sig = crypto.createHmac('sha256', sk).update(qs).digest('base64'); return json(res, 200, { url: 'https://buy.moonpay.com/' + qs + '&signature=' + encodeURIComponent(sig), signed: true, pol }); } return json(res, 200, { url: 'https://www.moonpay.com/buy/matic', signed: false, pol }); // MoonPay's Polygon page still lives at the old slug; /buy/pol renders their 404 (same fix as RM Circle) } if (p === '/api/catalog' && req.method === 'GET') { // five live quotes per call: serve a 20-second cache so pages that load the // ladder (join, home, buy) are not waiting on the RPC every time if (!catalogCache.at || Date.now() - catalogCache.at > 20000) { try { catalogCache.products = await chain.catalog(); catalogCache.at = Date.now(); } catch (e) { if (!catalogCache.products) throw e; } } return json(res, 200, { products: catalogCache.products }); } if (p === '/api/feed' && req.method === 'GET') { return json(res, 200, { events: await attachNames(chain.recentEvents(Number(u.searchParams.get('n')) || 100)) }); } if (p === '/api/feed/live' && req.method === 'GET') { res.writeHead(200, baseHeaders({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-store', Connection: 'keep-alive' })); res.write(': connected\n\n'); feedClients.add(res); req.on('close', () => feedClients.delete(res)); return; } m = /^\/api\/tx\/(0x[0-9a-fA-F]{64})$/.exec(p); if (m && req.method === 'GET') { // tx relay so the browser never talks to the RPC directly (CSP stays 'self'). // Serves both the wallet's waitTx poll (found/status) and the built-in // /tx/ viewer (full details + decoded events). try { const r = await chain.rpc('eth_getTransactionReceipt', [m[1]]); if (!r) return json(res, 200, { found: false }); const out = { found: true, status: r.status, blockNumber: r.blockNumber, gasUsed: r.gasUsed }; try { const t = await chain.rpc('eth_getTransactionByHash', [m[1]]); if (t) { out.from = t.from; out.to = t.to; out.valueWei = BigInt(t.value || '0x0').toString(); } } catch (e) {} try { const blk = await chain.rpc('eth_getBlockByNumber', [r.blockNumber, false]); if (blk) out.ts = blk.timestamp; } catch (e) {} try { out.events = await attachNames((r.logs || []).map(chain.decodeLog).filter(Boolean) .map(ev => Object.assign(ev, { tx: m[1] }))); } catch (e) { out.events = []; } return json(res, 200, out); } catch (e) { return json(res, 200, { found: false, rpcError: true }); } } if (p === '/api/sponsor' && req.method === 'GET') { // The account's stored sponsor is authoritative โ€” it persists across // devices, cleared cookies, and return visits. Fall back to the first-touch // cookie only for anonymous visitors with no account sponsor yet. (Reading // the cookie alone was orphaning buyers to root when the cookie was absent.) const s = await auth.fromRequest(req); const acct = s && s.email ? await accounts.byEmail(s.email) : null; const tok = (acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor'] || ''; const spd = await resolveSponsorDetailed(tok); let sponsorId = spd.id; let sponsorBlocked = null; // set only for transient/unknown failures now: the client refuses the transaction let sponsorRouted = null; // set when an unactivated sponsor was walked past: the client tells the buyer, then proceeds if (acct && acct.sponsorRef && !sponsorId && spd.reason === 'notActivated') { const w = await walkUpActivatedSponsor(tok); if (w.reason === 'rpc') sponsorBlocked = 'rpc'; else { const catchId = Number(siteConfig().defaultSponsorId) || 1; sponsorId = w.id; sponsorRouted = { from: spd.name || null, to: w.id ? w.name : 'the company (#' + catchId + ')', toId: w.id || catchId, skipped: w.skipped.map(x => x.name) }; if (u.searchParams.get('intent') === 'buy') sponsorRoutedAlert(acct.email, spd, sponsorRouted, w.skipped); // the dashboard's display-only pre-check never alerts (Marty, 2026-09-21) } } else if (acct && acct.sponsorRef && !sponsorId && spd.reason !== 'none') { sponsorBlocked = spd.reason; if (u.searchParams.get('intent') === 'buy') sponsorBlockedAlert(acct.email, Object.assign({ tok }, spd)); } if (sponsorId && await payoutChainBlocked(sponsorId, 2)) { console.log('sponsor routed away from no-payout chain', sponsorId); sponsorId = 0; } // orphan fallback: an unresolvable/absent sponsor (dead link, no link) lands // the new member under the configured catch position (#1) instead of root if (!sponsorId && (!acct || acct.memberId !== (Number(siteConfig().defaultSponsorId) || 1))) sponsorId = Number(siteConfig().defaultSponsorId) || 1; let name = null, avatarUrl = null, own = false; // the join page names the owner of the LINK it was opened with (?ref=token), not the visitor's own // sponsor: a member previewing their own invite page was seeing their upline's name (Jim, 2026-09-13) let showTok = String(u.searchParams.get('ref') || '').trim().toLowerCase(); if (showTok && JOIN_ALIASES[showTok]) showTok = JOIN_ALIASES[showTok]; const nameTok = showTok || tok; if (nameTok) { const t = nameTok.toLowerCase(); let a = await accounts.byCode(t); if (!a) a = await accounts.byUsername(t); if (!a && /^\d+$/.test(nameTok)) a = await accounts.byMemberId(Number(nameTok)); if (a) { name = a.username ? '@' + a.username : (a.memberId ? 'member #' + a.memberId : null); avatarUrl = a.avatarUrl || null; own = !!(acct && a.email === acct.email); var bio = null, cobrand = false; try { cobrand = (await ads.milestonesOf(a.email)).includes('level3'); if (cobrand) bio = a.bio ? String(a.bio).slice(0, 220) : null; } catch (e) {} } } // "You're joining the line of X" must only greet someone who is actually about to // join. The last-touch sponsor cookie lives 30 days, so an EXISTING member who once // clicked a teammate's link was being told on the sign-in page that logging in would // place them under that person (Marty, 2026-09-17). Alarming, and untrue: their // sponsor locked at their first purchase and nothing here can move it. // Show it when they just arrived through a link (?ref= in the URL), or when the cookie // is there AND this browser has never had an account. Attribution itself is untouched. let invited = !!showTok; if (!invited && tok) { try { invited = !(await fraud.hasAccountOnDevice(req)); } catch (e) { invited = true; } } if (acct) invited = false; // already a member: they are not joining anybody's line return json(res, 200, { ref: tok, sponsorId, sponsorBlocked, sponsorRouted, sponsorName: spd.name || null, invited, name, avatarUrl, own, bio: typeof bio === 'undefined' ? null : bio, cobrand: typeof cobrand === 'undefined' ? false : cobrand }); } if (p === '/api/stats' && req.method === 'GET') { let members = 0; try { members = await chain.memberCount(); } catch (e) {} return json(res, 200, Object.assign({ onchainMembers: members, siteAccounts: await accounts.count() }, chain.totals())); } // -- 24/7 assistant if (p === '/api/chat' && req.method === 'POST') { const ip = req.socket.remoteAddress || 'x'; if (chatLimited(ip)) return json(res, 429, { error: 'Give it a minute, then ask again.' }); const b = await readBody(req); const r = await chatbot.answer(b.message, b.history); return json(res, r.error ? 400 : 200, r); } // -- accounts: email + password is the normal join path (wallet comes // out only at purchase / payout-activation time and gets linked then) if (p === '/api/signup' && req.method === 'POST') { const b = await readBody(req); if (fraud.isBlocked(b.email)) return json(res, 403, { error: 'This address cannot open an account.' }); const ref = parseCookies(req)['iap.sponsor'] || ''; // last-touch attribution, locked at account creation const r = await accounts.signup(b.email, b.password, ref); if (r.error) return json(res, 400, r); // sponsor is notified once the new member picks a username (onboarding), // so the email can name them โ€” see /api/my/profile if (b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent const token = await auth.mintSession({ email: r.account.email }); return json(res, 200, { ok: true, account: r.account }, { 'Set-Cookie': auth.sessionCookie(token) }); } if (p === '/api/login' && req.method === 'POST') { const b = await readBody(req); const r = await accounts.login(b.email, b.password); if (r.error) return json(res, 400, r); let memberId = 0; if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (e) {} } const token = await auth.mintSession({ email: r.account.email, address: r.account.address, memberId }); return json(res, 200, { ok: true, account: r.account }, { 'Set-Cookie': auth.sessionCookie(token) }); } // -- passwordless: email code sign-in (signup and login are the same act) if (p === '/api/auth/email/start' && req.method === 'POST') { const b = await readBody(req); const e = String(b.email || '').trim().toLowerCase(); const devHdr = fraud.deviceOf(req) ? undefined : { 'Set-Cookie': fraud.deviceCookie(fraud.newDeviceId(), IS_PROD) }; // browser id for one-account-per-person checks if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(e)) return json(res, 400, { error: 'That email address does not look right.' }); if (fraud.isBlocked(e)) return json(res, 403, { error: 'This address cannot open an account.' }); const prev = emailCodes.get(e); if (prev && Date.now() < prev.nextAt) { console.log('signup-guard cooldown', clientIp(req), e.replace(/^(.).*(@.*)$/, '$1***$2')); return json(res, 429, { error: 'Code already sent. Give it a minute, then try again.' }, devHdr); } const guard = codeGuard(req, b); // honeypot, form age, per-IP + global limits, icon check once limited if (guard) return json(res, guard.status, guard.body, devHdr); const code = String(Math.floor(100000 + Math.random() * 900000)); emailCodes.set(e, { code, exp: Date.now() + 15 * 60 * 1000, tries: 0, nextAt: Date.now() + 60 * 1000 }); if (mailer.hasKey()) { try { await mailer.sendCode(e, code); } catch (err) { console.error('sendCode failed', err.message); return json(res, 502, { error: 'Could not send the email. Try again in a minute.' }, devHdr); } return json(res, 200, { ok: true, sent: true }, devHdr); } if (!IS_PROD) return json(res, 200, { ok: true, sent: false, devCode: code }, devHdr); return json(res, 503, { error: 'Email sign-in is not configured yet.' }); } if (p === '/api/auth/email/verify' && req.method === 'POST') { const b = await readBody(req); const e = String(b.email || '').trim().toLowerCase(); const rec = emailCodes.get(e); if (!rec || rec.exp < Date.now()) return json(res, 400, { error: 'Code expired. Request a fresh one.' }); rec.tries += 1; if (rec.tries > 6) { emailCodes.delete(e); return json(res, 400, { error: 'Too many tries. Request a fresh code.' }); } if (String(b.code || '').trim() !== rec.code) return json(res, 400, { error: 'That code does not match.' }); emailCodes.delete(e); const ref = parseCookies(req)['iap.sponsor'] || ''; const via = parseCookies(req)['iap.angle'] || ''; const joinedRef = decodeURIComponent(parseCookies(req)['iap.ref'] || '') || null; if (fraud.isSuspended(e)) return json(res, 403, { error: 'This account is suspended. Contact support.' }); let fraudFlags = []; const existing = await accounts.byEmail(e); if (!existing) { // anti-fraud checks apply to NEW accounts only (Marty, 2026-09-16: one account per person) let spAcct = null; if (ref) { try { spAcct = await accounts.byCode(String(ref).toLowerCase()); if (!spAcct) spAcct = await accounts.byUsername(String(ref).toLowerCase()); } catch (err) {} } const fc = await fraud.checkSignup(req, spAcct, siteConfig(), e); fraudFlags = fc.flags; // an approved exception is worth a log line: it is the difference between "the guard // is broken" and "Marty said yes to this person" if (fc.allowlisted) console.log('signup allowed by exception list', e.replace(/^(.).*(@.*)$/, '$1***$2'), 'via', fc.allowedBy); if (fc.block) { console.log('signup blocked', fc.flags.join(','), clientIp(req), e.replace(/^(.).*(@.*)$/, '$1***$2')); fraudAlert(e, fc, spAcct, true); return json(res, 403, { error: fc.block }); } } const r = await accounts.ensure(e, ref, via, joinedRef); // first touch wins; existing accounts unchanged if (r.error) return json(res, 400, r); if (r.created) { fraud.recordSignup(e, req, fraudFlags).catch(() => {}); if (fraudFlags.length) fraudAlert(e, { flags: fraudFlags, ip: fraud.ipOf(req) }, null, false); } else fraud.recordSeen(e, req).catch(() => {}); // the lead is in the door: queue the getting-started sequence (opt-in box is pre-checked on both forms) if (r.created && (b.followups || b.newsletter)) drip.enqueue(e, ref, via).catch(() => {}); // a wallet-only session (signed with a wallet, no account) finishing setup: // adopt that wallet into the email account so member #, purchases and // payouts stay attached, then retire the wallet-only session const prior = await auth.fromRequest(req); if (prior && prior.address && !prior.email) { const lr = await accounts.linkWallet(e, prior.address); if (lr.error) return json(res, 400, lr); r.account = lr.account || await accounts.byEmail(e); await auth.logout(req); } if (r.created) { sendWelcome(e, ref).catch(() => {}); } // sponsor notified at username set (/api/my/profile) // partner promo code carried on the join link: redeem once per account (ignored if invalid/used) try { const pc = parseCookies(req)['iap.promo']; if (pc) { const pref = parseCookies(req)['iap.promoref'] ? decodeURIComponent(parseCookies(req)['iap.promoref']) : ''; const g = await promos.redeem(pc, e, 'link', pref); if (g.ok) { const paid = await payPromo(e, todayCT()); console.log('promo redeemed', g.code, 'day 1:', paid.map(x => x.amount).join('+') || 0, 'of', g.credits, e, pref ? 'from ' + pref : ''); } } } catch (err) { console.error('promo redeem', err.message); } // legacy bridge: a listed former Faucet Wave / Tier One Ads member gets welcome-back credits once if (r.created && /^(fw|t1)-(adv|earn)$/.test(via)) { try { const g = legacy.grant(e, siteConfig()); if (g) { await ads.addEarned(e, g.credits, { log: { kind: 'grant', note: 'Welcome-back credits (' + g.brand + ')' } }); console.log('legacy grant', g.brand, g.seg, g.credits, e); } } catch (err) { console.error('legacy grant', err.message); } } if (r.created && b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent, new joins only let memberId = 0; if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} } const token = await auth.mintSession({ email: r.account.email, address: r.account.address, memberId }); return json(res, 200, { ok: true, account: r.account, created: !!r.created }, { 'Set-Cookie': auth.sessionCookie(token) }); } // -- wallet auth: link-to-account when an email session exists, or // wallet-first sign-in (no UI door since 2026-09-09; a wallet-only session // is walked to the email card, which adopts the wallet on verify) if (p === '/api/auth/challenge' && req.method === 'POST') { const b = await readBody(req); const r = auth.makeChallenge(b.address); return json(res, r.error ? 400 : 200, r); } if (p === '/api/auth/verify' && req.method === 'POST') { const b = await readBody(req); const r = await auth.verifyChallenge(b.address, b.signature); if (r.error) return json(res, 400, r); let memberId = 0; try { memberId = await chain.memberIdByAccount(r.address); } catch (e) {} const s = await auth.fromRequest(req); if (s && s.email && b.asPosition) { // Qualified Start: a second (thirdโ€ฆ) wallet on the same account. It becomes // its own on-chain member under this member's id when it buys; the session // stays on the main wallet. // policy (Marty, 2026-09-10): positions exist to qualify, so at most five per account, and a // wallet that is already registered on-chain under anything other than this account's main // member is refused (a position-under-position chain would recapture 70% of a self-buy) const MAX_POS = 5; const have = await accounts.positions(s.email); if (!have.find(p => p.address === r.address.toLowerCase()) && have.length >= MAX_POS) return json(res, 400, { error: 'You can link up to ' + MAX_POS + ' positions, which is everything Qualified Start needs. Once qualified, buy from your main wallet so your sponsor is paid in full.' }); if (memberId) { const mainId = await auth.refreshMemberId(s); let mm = null; try { mm = await chain.member(memberId); } catch (e) {} if (mm && mainId && mm.sponsorId !== mainId) return json(res, 400, { error: 'That wallet is already registered on-chain under ' + (mm.sponsorId ? 'member #' + mm.sponsorId : 'no sponsor') + ', not under your main member #' + mainId + '. Only positions registered directly under you can be linked.' }); } const pr = await accounts.addPosition(s.email, r.address); if (pr.error) return json(res, 400, pr); if (memberId) await accounts.setPositionMember(r.address, memberId); return json(res, 200, { ok: true, position: true, address: r.address, memberId }); } if (s && s.email) { const lr = await accounts.linkWallet(s.email, r.address); if (lr.error) return json(res, 400, lr); await auth.updateSession(s.token, { address: r.address, memberId }); return json(res, 200, { ok: true, linked: true, address: r.address, memberId, next: parseCookies(req)['iap.return'] === 'polhunter' ? '/api/my/polhunter' : null }); } const acct = await accounts.byAddress(r.address); if (!acct && await accounts.positionOwner(r.address)) return json(res, 400, { error: 'That wallet is a linked position on an account. Sign in with that account\'s email instead.' }); const token = await auth.mintSession({ email: acct ? acct.email : null, address: r.address, memberId }); return json(res, 200, { ok: true, address: r.address, memberId }, { 'Set-Cookie': auth.sessionCookie(token) }); } if (p === '/api/auth/logout' && req.method === 'POST') { await auth.logout(req); return json(res, 200, { ok: true }, { 'Set-Cookie': auth.clearCookie() }); } if (p === '/api/gas' && req.method === 'GET') { try { return json(res, 200, await chain.suggestedFees()); } catch (e) { return json(res, 200, {}); } } if (p === '/api/me' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s) return json(res, 200, { signedIn: false }); const memberId = await auth.refreshMemberId(s); const acct = (s.email && await accounts.byEmail(s.email)) || (s.address && await accounts.byAddress(s.address)) || null; const spdMe = await resolveSponsorDetailed((acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor']); let sponsorId = spdMe.id; let sponsorBlocked = (acct && acct.sponsorRef && !sponsorId && spdMe.reason !== 'none') ? spdMe.reason : null; let sponsorRouted = null; if (sponsorBlocked === 'notActivated') { // never hold a buyer for an unactivated sponsor: walk up the line const w = await walkUpActivatedSponsor(acct.sponsorRef); if (w.reason === 'rpc') sponsorBlocked = 'rpc'; else { sponsorBlocked = null; sponsorId = w.id; sponsorRouted = { from: spdMe.name || null, to: w.id ? w.name : 'the company', toId: w.id || (Number(siteConfig().defaultSponsorId) || 1) }; } } if (sponsorId && await payoutChainBlocked(sponsorId, 2)) sponsorId = 0; const _defSpon = Number(siteConfig().defaultSponsorId) || 1; if (!sponsorId && memberId !== _defSpon) sponsorId = _defSpon; // orphan fallback โ†’ #1 (never self-sponsor) if (memberId && acct && acct.memberId !== memberId) accounts.setMemberId(acct.email, memberId).catch(() => {}); const out = { signedIn: true, returnTo: parseCookies(req)['iap.return'] === 'polhunter' ? 'polhunter' : null, sponsorBlocked, sponsorRouted, sponsorName: spdMe.name || null, email: s.email || (acct && acct.email) || null, address: s.address || (acct && acct.address) || null, memberId, username: (acct && acct.username) || null, refCode: (acct && acct.code) || null, sponsorId, // profile + line-banner fields so the Profile pane repopulates on reload (were being saved but not returned) avatarUrl: (acct && acct.avatarUrl) || null, bio: (acct && acct.bio) || null, socials: (acct && acct.socials) || null, wallOffers: parseWallOffers(acct), lineBannerUrl: (acct && acct.lineBannerUrl) || null, lineTargetUrl: (acct && acct.lineTargetUrl) || null }; if (memberId) { try { const mm = await chain.member(memberId); out.buyerCount = mm.buyerCount; out.onchainSponsorId = mm.sponsorId; const bal = await ads.balances((await myMemberIds(s)).ids, s.email); out.credits = bal.total; out.creditedCredits = bal.credited; out.earnedCredits = bal.earned; out.inCampaigns = bal.inCampaigns; out.availableCredits = bal.available; } catch (e) { out.chainReadError = true; } } return json(res, 200, out); } if (p === '/api/my/dashboard' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s) return json(res, 401, { error: 'Sign in first.' }); const memberId = await auth.refreshMemberId(s); const acct = (s.email && await accounts.byEmail(s.email)) || (s.address && await accounts.byAddress(s.address)) || null; if (memberId && acct && acct.memberId !== memberId) accounts.setMemberId(acct.email, memberId).catch(() => {}); let tankWaiting = null; // top of every Overview: people waiting for a sponsor (Marty, 2026-09-12) try { if (!tankWaitCache || Date.now() - tankWaitCache.ts > 60000) tankWaitCache = { ts: Date.now(), list: await tank.waiting() }; const tw = tankWaitCache.list; tankWaiting = { count: tw.length, names: tw.slice(0, 6).map(w => w.name), eligible: !!(await tank.eligibility(s.email)).ok }; } catch (e) {} const sc0 = siteConfig(); const out = { memberId, tankWaiting, email: s.email || (acct && acct.email) || null, pipeline: { live: pipeline.visible(sc0.pipelineMode, s.email || (acct && acct.email), ADMIN_EMAIL), mode: sc0.pipelineMode || 'off', eta: sc0.pipelineEta || '' }, address: s.address || (acct && acct.address) || null, username: (acct && acct.username) || null, refCode: (acct && acct.code) || null, lineBannerUrl: (acct && acct.lineBannerUrl) || null, /* the launch checklist mark reads it (was 7 of 8 with a banner set, 2026-09-14) */ credits: 0, buyerCount: 0, earnedWei: '0', earnCount: 0, referrals: [], welcomeCredits: 0 }; if (out.email) { // welcome credits unlock via the welcome tour when an upline with a // line banner exists; members with no tour to walk get them instantly const welcomed = await ads.welcomeGranted(out.email); const tour = welcomed ? [] : (await uplineSlides(out.email)).filter(a => a.lineTargetUrl); if (welcomed || !tour.length) { await ads.grantWelcome(out.email); out.welcomeCredits = ads.rates().welcomeCredits || 0; } // the welcome amount itself; the rest of the earned pool is viewing rewards, milestones and credits we add else { out.welcomeCredits = 0; out.gauntletPending = true; } } if (out.email) out.inboxUnread = await ads.unreadCount(out.email); // delivers pending solos too if (out.email) { // sponsor chat: presence heartbeat + unread + my availability + direct sponsor accounts.touchSeen(out.email).catch(() => {}); out.chatUnread = await messages.chatUnread(out.email); out.chatAvailable = (acct && acct.chatAvailable !== false); const spon = await accounts.sponsorOf(out.email); if (spon && spon.email) out.sponsor = { email: spon.email, name: spon.username ? '@' + spon.username : (spon.memberId ? 'member #' + spon.memberId : 'your sponsor'), online: (Date.now() - (spon.lastSeen || 0)) < 60000, available: spon.chatAvailable !== false }; } if (out.email) { // the one modal that interrupts: a human broadcast, or a money-at-stake alert const un = await messages.newestUnread(out.email); if (un) { const nm = un.fromMember ? await accounts.namesForMembers([un.fromMember]) : {}; out.sponsorMsg = { id: un.id, subject: un.subject, body: un.body, kind: un.kind || 'broadcast', fromName: (un.fromMember && nm[un.fromMember]) ? '@' + nm[un.fromMember] : (un.fromMember ? 'member #' + un.fromMember : 'your sponsor') }; } } if (memberId) { try { const mm = await chain.member(memberId); out.buyerCount = mm.buyerCount; const mine = await myMemberIds(s); // linked positions are the member's own: their qualifying buyers count toward the account (Jim, 2026-09-14) out.buyerCountMain = mm.buyerCount; out.buyerCountPositions = 0; for (const id of mine.ids) if (id && id !== memberId) { try { out.buyerCountPositions += (await chain.member(id)).buyerCount || 0; } catch (e) {} } out.buyerCountAll = out.buyerCountMain + out.buyerCountPositions; // badges count every position; levels the contract pays #main on depend on buyerCount alone const bal = await ads.balances(mine.ids, s.email); out.credits = bal.total; out.creditedCredits = bal.credited; out.earnedCredits = bal.earned; out.inCampaigns = bal.inCampaigns; out.availableCredits = bal.available; } catch (e) { out.chainReadError = true; } let earned = 0n, n = 0; for (const ev of chain.recentEvents(1e9)) { // whole history, not the last 600 if ((ev.type === 'TierPaid' && ev.recipientId === memberId) || (ev.type === 'AwardPaid' && ev.toId === memberId)) { earned += BigInt(ev.amountWei); n += 1; } } out.earnedWei = earned.toString(); out.earnCount = n; } // achievement milestones (same ladder as the Overview stepper) + one-time credit // bonuses โ€” computed AFTER buyerCount is read from chain above (else always 0) { const bc = out.buyerCountAll != null ? out.buyerCountAll : (out.buyerCount || 0); const reached = []; if (out.memberId) reached.push('payouts'); if (bc >= 1) reached.push('firstBuyer'); if (bc >= 2) reached.push('level2'); if (bc >= 5) reached.push('level3'); try { if (out.memberId && friday.earnedBadge(out.memberId)) reached.push('fridays'); } catch (e) {} // five Five Dollar Fridays in a row try { for (const k of await ads.milestonesOf(out.email)) if (!reached.includes(k)) reached.push(k); } catch (e) {} // a badge once earned stays earned out.milestonesReached = reached; if (out.email && reached.length) out.milestonesGranted = await ads.grantMilestones(out.email, reached); } // who joined through this member: their invite link uses username when set, // else code, and the numeric id once on-chain โ€” match all three const refs = []; if (acct && acct.code) refs.push(acct.code); if (acct && acct.username) refs.push(acct.username); if (memberId) refs.push(String(memberId)); const joined = await accounts.listByReferrer(refs); out.referrals = joined.map(r => ({ name: r.username || r.email.replace(/^(.).*(@.*)$/, '$1***$2'), // username, else privacy mask joined: r.created, status: r.address ? 'wallet linked' : 'joined free' })); out.isAdmin = !!(ADMIN_EMAIL && out.email && String(out.email).toLowerCase() === ADMIN_EMAIL); // shows the Admin link out.wallUnlocked = wallUnlockedFor(out.buyerCount || 0); // how many wall positions are the member's own return json(res, 200, out); } // -- linked positions (Qualified Start): list, refresh from chain, unlink if (p === '/api/my/positions' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const acct = await accounts.byEmail(s.email); const mainId = await auth.refreshMemberId(s); const list = await accounts.positions(s.email); const out = []; const posIds = [mainId, ...list.map(p => p.memberId)].filter(Boolean); let balPer = {}, credited = 0; try { const bb = await ads.balances(posIds, s.email); credited = bb.credited; for (const p of bb.per) balPer[p.memberId] = p.avail; } catch (e) {} for (const pos of list) { let id = pos.memberId; if (!id) { try { id = await chain.memberIdByAccount(pos.address); if (id) await accounts.setPositionMember(pos.address, id); } catch (e) {} } const row = { address: pos.address, memberId: id || 0, buyerCount: 0, counted: false, credits: 0, created: pos.created }; if (id) { try { const mm = await chain.member(id); row.buyerCount = mm.buyerCount; row.counted = mm.countedAsBuyer; row.sponsorId = mm.sponsorId; } catch (e) {} row.credits = balPer[id] != null ? balPer[id] : 0; row.noPayout = await payoutChainBlocked(id, 3); // linkage only: a buy from here would pay a listed wallet } out.push(row); } const main = { address: (acct && acct.address) || null, memberId: mainId, credits: 0, buyerCount: 0 }; if (mainId) { main.credits = balPer[mainId] != null ? balPer[mainId] : 0; try { main.buyerCount = (await chain.member(mainId)).buyerCount; } catch (e) {} main.noPayout = await payoutChainBlocked(mainId, 3); } // live POL balances (the link signature proved the wallet is theirs) + a POL/USD rate from the catalog const bal = async a => { try { return BigInt(await chain.rpc('eth_getBalance', [a, 'latest'])).toString(); } catch (e) { return null; } }; if (main.address) main.balanceWei = await bal(main.address); for (const row of out) row.balanceWei = await bal(row.address); let polUsd = 0; try { const cat = await chain.catalog(); const pk = cat.find(x => x.costWei); if (pk) polUsd = (pk.priceCents / 100) / (Number(BigInt(pk.costWei)) / 1e18); } catch (e) {} return json(res, 200, { main, positions: out, polUsd, totalCredits: main.credits + out.reduce((n, r) => n + r.credits, 0), credited }); } if (p === '/api/my/positions/remove' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await accounts.removePosition(s.email, b.address); return json(res, r.error ? 400 : 200, r); } // -- coaching: every direct's ladder rung, stalled flag, and what to say // -- holding tank: waiting members, my adoptions, adopt, release (pay it forward) // -- promo code typed on the dashboard if (p === '/api/my/promo/redeem' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const g = await promos.redeem(b.code, s.email, 'dashboard', ''); if (g.error) return json(res, 400, g); const paid = await payPromo(s.email, todayCT()); return json(res, 200, { ok: true, credits: g.credits, now: paid.reduce((n, x) => n + x.amount, 0), step: g.step, steps: g.steps, code: g.code, partner: g.partner }); } // -- admin: member card. GET ?q= resolves email / @username / #id / share code / wallet; // PATCH edits username, sponsor, main wallet or grants credits; DELETE removes a free account (2026-09-13) if (p === '/api/admin/member' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const q = u.searchParams.get('q') || u.searchParams.get('email') || ''; const a = await adminMember.resolve(q); if (!a) return json(res, 404, { error: 'No member matches "' + q.slice(0, 60) + '".' }); return json(res, 200, await adminMember.view(a.email)); } if (p === '/api/admin/member' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const e = String(b.email || '').trim().toLowerCase(); const acct = e && await accounts.byEmail(e); if (!acct) return json(res, 404, { error: 'No such member.' }); if (b.username !== undefined) { const un = String(b.username || '').trim().toLowerCase(); if (!/^[a-z0-9_]{3,20}$/.test(un)) return json(res, 400, { error: 'Username: 3 to 20 letters, numbers or underscore.' }); const r = await accounts.setUsername(e, un); if (r.error) return json(res, 400, r); console.log('admin username', e, un); } if (b.sponsorRef !== undefined) { const r = await accounts.setSponsorRef(e, String(b.sponsorRef || '').trim()); if (r.error) return json(res, 400, r); console.log('admin sponsor', e, String(b.sponsorRef || '').trim()); } if (b.address !== undefined) { const a = String(b.address || '').trim().toLowerCase(); if (a && !/^0x[0-9a-f]{40}$/.test(a)) return json(res, 400, { error: 'That is not a wallet address.' }); const r = await accounts.adminSetAddress(e, a || null); if (r.error) return json(res, 400, r); let mid = 0; if (a) { try { mid = Number(await chain.memberIdByAccount(a)) || 0; } catch (x) {} } await accounts.setMemberId(e, mid); if (db.enabled()) await db.q('UPDATE sessions SET address=?, member_id=? WHERE email=?', [a || null, mid || null, e]).catch(() => {}); console.log('admin wallet swap', e, a || '(none)', 'member', mid); } if (b.grantCredits !== undefined) { const n = Math.round(Number(b.grantCredits)); if (!(n > 0) || n > 100000) return json(res, 400, { error: 'Credits: a whole number from 1 to 100,000.' }); // The member sees this note in their Credit activity, so let the reason travel with // the grant. A correction that reads "granted by admin" explains nothing; one that // names itself is the difference between a refund and a mystery (Marty, 2026-09-17). const why = String(b.note || '').trim().slice(0, 120) || 'Credits granted by admin'; await ads.addEarned(e, n, { log: { kind: 'grant', note: why } }); console.log('admin credits', e, n, String(b.note || '').slice(0, 100)); } return json(res, 200, await adminMember.view(e)); } if (p === '/api/admin/member' && req.method === 'DELETE') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const e = String(u.searchParams.get('email') || '').trim().toLowerCase(); const acct = e && await accounts.byEmail(e); if (!acct) return json(res, 404, { error: 'No such member.' }); if (acct.memberId) return json(res, 400, { error: 'Member #' + acct.memberId + ' is registered on-chain and cannot be deleted.' }); const pos = await accounts.positions(e); if (pos.some(x => x.memberId)) return json(res, 400, { error: 'This account owns a registered position and cannot be deleted.' }); const r = await accounts.removeAccount(e); if (r.error) return json(res, 400, r); if (db.enabled()) await db.q('DELETE FROM sessions WHERE email=?', [e]).catch(() => {}); console.log('admin removed account', e); return json(res, 200, { ok: true }); } // -- release notes + roadmap (public read; admin write) (Marty, 2026-09-14) if (p === '/api/releases' && req.method === 'GET') return json(res, 200, releases.publicView()); // -- promo toolkit: tiers by badge + the AI Copy Engine (Surge+), credits beyond the free allowance if (p === '/api/my/toolkit' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await toolkit.status(s.email)); } if (p === '/api/my/toolkit/generate' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await toolkit.generate(s.email, String(b.kind || ''), String(b.brief || ''), String(b.angle || 'plain')); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/toolkit/template' && req.method === 'POST') { // Spark: one-tap campaign aimed at the member's link const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const memberId = await auth.refreshMemberId(s); const r = await toolkit.template(s.email, memberId, (await myMemberIds(s)).ids, String(b.kind || ''), Number(b.budget) || 0); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/toolkit/split' && req.method === 'GET') { // Circuit: per-angle views/joins/buyers for the member's link const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await toolkit.split(s.email)); } if (p === '/api/my/toolkit/videos' && req.method === 'GET') { // Circuit: Video Maker list const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await toolkit.videos(s.email)); } if (p === '/api/my/toolkit/video' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await toolkit.makeVideo(s.email, String(b.slug || '')); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/toolkit/team' && req.method === 'GET') { // Nexus: Leader Ops const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await toolkit.team(s.email)); } if (p === '/api/my/toolkit/nudge' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const memberId = await auth.refreshMemberId(s); const r = await toolkit.nudge(s.email, memberId, String(b.email || ''), String(b.text || '')); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/toolkit/grant' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await toolkit.grant(s.email, String(b.email || ''), Number(b.credits) || 0); if (r.ok) pushFeed({ type: 'Grant', from: r.fromName, to: r.toName, credits: r.credits, ts: Date.now() }); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/toolkit/promo' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await toolkit.partnerCode(s.email, String(b.code || ''), Number(b.credits) || 0); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/toolkit' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await toolkit.adminUsage()); } if (p === '/api/leaderboard' && req.method === 'GET') { // public standings; signed-in members also get their own row const s = await auth.fromRequest(req); const period = ['week', 'month', 'all', 'lastweek', 'lastmonth'].includes(u.searchParams.get('period')) ? u.searchParams.get('period') : 'week'; return json(res, 200, await leaderboard.view(period, s && s.email ? s.email : null)); } if (p === '/api/admin/updates' && req.method === 'GET') { // member update emails: notes, audiences, log if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const since = updates.lastSentAt(); return json(res, 200, Object.assign({ notes: releases.notes().map(n => ({ id: n.id, title: n.title, date: n.date, fresh: !since || (n.created || 0) > since || (n.date && new Date(n.date + 'T12:00:00Z').getTime() > since) })), audiences: updates.AUDIENCES, counts: await updates.counts(), mailer: mailer.hasKey(), lastSentAt: since, draft: updates.draft() }, updates.status())); } if (p === '/api/admin/updates/draft' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { ok: true, draft: updates.saveDraft(await readBody(req)) }); } if (p === '/api/admin/updates/preview' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); return json(res, 200, updates.compose({ subject: b.subject, intro: b.intro, closing: b.closing, noteIds: (b.noteIds || []).map(String) }, { email: ADMIN_EMAIL, username: 'you' })); } if (p === '/api/admin/updates/send' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = await updates.send({ subject: b.subject, intro: b.intro, closing: b.closing, noteIds: (b.noteIds || []).map(String), audience: b.audience, to: Array.isArray(b.to) ? b.to.slice(0, 5000) : null }, { test: !!b.test }); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/releases' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { notes: releases.notes(), roadmap: releases.roadmap(), tags: releases.TAGS, statuses: releases.STATUSES }); } if (p === '/api/admin/releases' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = b.kind === 'roadmap' ? releases.saveRoadmap(b) : releases.saveNote(b); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/releases' && req.method === 'DELETE') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, releases.remove(u.searchParams.get('kind'), String(u.searchParams.get('id') || ''))); } // -- admin: blog (list all incl. drafts, save/create, delete) (Marty, 2026-09-12) if (p === '/api/admin/blog' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const slug = u.searchParams.get('slug'); if (slug) { const post = await blog.get(slug); return post ? json(res, 200, { post }) : json(res, 404, { error: 'No such post.' }); } return json(res, 200, { syndication: syndicate.enabled(), posts: (await blog.listAll()).map(x => ({ slug: x.slug, title: x.title, status: x.status, tags: x.tags, publishedAt: x.publishedAt, updated: x.updated, views: x.views, excerpt: x.excerpt, cover: x.cover, syndicated: syndicate.statusOf(x.slug) })) }); } if (p === '/api/admin/blog' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const before = b.existingSlug ? await blog.get(b.existingSlug) : null; const r = await blog.save(b, b.existingSlug || null); if (r.ok && r.post.status === 'published' && (!before || before.status !== 'published') && !b.noSyndicate) { // first time this article goes live: push it to X + Instagram (never repeated for the same slug) syndicate.publish(r.post).catch(e => console.error('syndication', e.message)); r.syndicating = syndicate.enabled(); } r.syndicated = syndicate.statusOf(r.post.slug); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/blog/syndicate' && req.method === 'POST') { // manual: post (or retry) a published article if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const post = await blog.get(String(b.slug || '')); if (!post) return json(res, 404, { error: 'No such post.' }); if (post.status !== 'published') return json(res, 400, { error: 'Publish the article first.' }); if (!syndicate.enabled()) return json(res, 400, { error: 'No Blotato key on the server.' }); const st = await syndicate.publish(post, { force: !!b.force }); return json(res, 200, { ok: true, syndicated: st }); } if (p === '/api/admin/blog' && req.method === 'DELETE') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const slug = u.searchParams.get('slug'); if (!slug) return json(res, 400, { error: 'slug' }); return json(res, 200, await blog.remove(slug)); } // -- admin: promo codes (create/update, switch on/off, redemptions) if (p === '/api/admin/promos' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await promos.adminView()); } if (p === '/api/admin/promos' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = await promos.create(b); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/promos' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = await promos.setActive(b.code, !!b.active); return json(res, r.error ? 400 : 200, r); } // -- username suggestion for the required first step (Marty, 2026-09-12): email prefix, cleaned, unique if (p === '/api/my/username-suggest' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); let base = String(s.email).split('@')[0].toLowerCase().replace(/[^a-z0-9_]/g, '').slice(0, 18); if (!/[a-z]/.test(base)) base = 'member' + base; if (base.length < 3) base = (base + 'xyz').slice(0, 3); let pick = base; for (let i = 2; i < 100 && await accounts.byUsername(pick); i++) pick = base.slice(0, 20 - String(i).length) + i; return json(res, 200, { suggest: pick }); } if (p === '/api/my/tank' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await tank.view(s.email)); } // -- achievement badge -> Telegram (payments topic + the main group), once per badge per member. // The browser composes the personalised image (canvas, same as Share) and posts it here. if (p === '/api/my/badge-post' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const key = String(u.searchParams.get('key') || ''); if (!BADGE_META[key]) return json(res, 400, { error: 'Unknown badge.' }); if (!/^image\/jpeg/.test(String(req.headers['content-type'] || ''))) return json(res, 400, { error: 'Send the badge as a JPEG.' }); let jpeg; try { jpeg = await readRaw(req, 1.5 * 1024 * 1024); } catch (e) { return json(res, 413, { error: 'Image too large.' }); } if (!jpeg || jpeg.length < 2000 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) return json(res, 400, { error: 'That is not a JPEG.' }); const held = await ads.milestonesOf(s.email); if (!held.includes(key)) return json(res, 400, { error: 'You have not unlocked that badge yet.' }); const log = badgeLog(); const mine = log[s.email] || {}; if (mine[key]) return json(res, 200, { ok: true, already: true }); // manual re-posts (the button) are Marty's only; members' badges go out automatically on unlock if (u.searchParams.get('manual') === '1' && s.email !== ADMIN_EMAIL) return json(res, 403, { error: 'Badges post automatically when they unlock.' }); const a = await accounts.byEmail(s.email); const who = a && a.username ? '@' + a.username : (a && a.memberId ? 'member #' + a.memberId : 'a member'); const link = a && a.username ? 'instantadpay.com/join/' + a.username : 'instantadpay.com'; const [label, sub] = BADGE_META[key]; const caption = '\u{1F3C6} InstantAdPay \u00b7 ' + who.replace(/[<>&]/g, '') + ' unlocked ' + label + ': ' + sub + '\n' + link; // the server draws the card (a phone's canvas can come out blank: cryptomonk, 2026-09-16); the upload is the fallback try { const srv = await badge.render(key, a && a.username ? a.username : (a && a.memberId ? 'member #' + a.memberId : '')); if (srv) jpeg = srv; } catch (e) {} const sc = siteConfig(); let sent = 0; if (sc.telegramBotToken && sc.telegramEchoChatId) { if (await telegramSendPhoto(sc.telegramEchoChatId, jpeg, caption, sc.telegramEchoTopicId)) sent++; // payments topic if (String(sc.telegramBadgeGeneral || '1') !== '0' && await telegramSendPhoto(sc.telegramEchoChatId, jpeg, caption, null)) sent++; // the main group (General) } mine[key] = { ts: Date.now(), sent }; log[s.email] = mine; try { fs.writeFileSync(BADGE_LOG(), JSON.stringify(log)); } catch (e) {} pushFeed({ type: 'Badge', member: who, label, ts: Date.now() }); console.log('badge posted', s.email, key, 'sent', sent); return json(res, 200, { ok: true, sent }); } // -- member's own share: store the composed badge so a public page can carry it as the preview image if (p === '/api/my/badge-image' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const key = String(u.searchParams.get('key') || ''); if (!BADGE_META[key]) return json(res, 400, { error: 'Unknown badge.' }); let jpeg; try { jpeg = await readRaw(req, 1.5 * 1024 * 1024); } catch (e) { return json(res, 413, { error: 'Image too large.' }); } if (!jpeg || jpeg.length < 2000 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) return json(res, 400, { error: 'That is not a JPEG.' }); if (!(await ads.milestonesOf(s.email)).includes(key)) return json(res, 400, { error: 'You have not unlocked that badge yet.' }); const a = await accounts.byEmail(s.email); if (!a || !a.username) return json(res, 400, { error: 'Pick a username first; the share page carries it.' }); try { const srv = await badge.render(key, a.username); if (srv) jpeg = srv; } catch (e) {} fs.writeFileSync(path.join(UPLOADS_DIR, 'badge-' + a.username + '-' + key + '.jpg'), jpeg); return json(res, 200, { ok: true, page: 'https://instantadpay.com/b/' + a.username + '/' + key, image: 'https://instantadpay.com/badge-img/' + a.username + '/' + key + '.jpg' }); } if (p === '/api/admin/badge-repost' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const key = String(b.key || ''); const a = await accounts.byEmail(String(b.email || '').toLowerCase()); if (!a || !BADGE_META[key]) return json(res, 400, { error: 'Unknown member or badge.' }); if (!(await ads.milestonesOf(a.email)).includes(key)) return json(res, 400, { error: 'That member has not unlocked that badge.' }); const jpeg = await badge.render(key, a.username || ('member #' + a.memberId)); if (!jpeg) return json(res, 500, { error: 'Render failed.' }); const who = a.username ? '@' + a.username : 'member #' + a.memberId; const link = a.username ? 'instantadpay.com/join/' + a.username : 'instantadpay.com'; const [label, sub] = BADGE_META[key]; const caption = '\u{1F3C6} InstantAdPay \u00b7 ' + who.replace(/[<>&]/g, '') + ' unlocked ' + label + ': ' + sub + '\n' + link; const sc = siteConfig(); let sent = 0; if (sc.telegramBotToken && sc.telegramEchoChatId) { if (await telegramSendPhoto(sc.telegramEchoChatId, jpeg, caption, sc.telegramEchoTopicId)) sent++; if (String(sc.telegramBadgeGeneral || '1') !== '0' && b.general !== false && await telegramSendPhoto(sc.telegramEchoChatId, jpeg, caption, null)) sent++; } const log = badgeLog(); const mine = log[a.email] || {}; mine[key] = { ts: Date.now(), sent, repost: true }; log[a.email] = mine; try { fs.writeFileSync(BADGE_LOG(), JSON.stringify(log)); } catch (e) {} return json(res, 200, { ok: true, sent, bytes: jpeg.length }); } if (p === '/api/my/badge-posted' && req.method === 'GET') { // which of my badges are already on Telegram const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, { posted: Object.keys(badgeLog()[s.email] || {}), canPost: s.email === ADMIN_EMAIL }); } if (p === '/api/my/tank/adopt' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); if (fraud.excluded(s.email)) return json(res, 403, { error: 'Adoptions are not available on this account. Contact support.' }); const r = await tank.adopt(s.email, b.who, b.note); if (r.ok) { // tell the payments topic who picked whom up (Marty, 2026-09-12) pushFeed({ type: 'Adopted', sponsor: r.adopterName, member: r.adopteeName, ts: Date.now() }); try { const sc = siteConfig(); const clean = t => String(t || '').replace(/[<>&]/g, ''); if (sc.telegramBotToken && sc.telegramEchoChatId) telegramSend(sc.telegramEchoChatId, '\u{1F91D} InstantAdPay \u00b7 ' + clean(r.adopterName) + ' picked up ' + clean(r.adopteeName) + ' from the holding tank and is now their sponsor', sc.telegramEchoTopicId).catch(() => {}); } catch (e) {} } return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/tank/release' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await tank.release(s.email, b.email); if (r.ok) { // the feed shows releases too, so a pickup-and-drop is visible for what it is (Marty, 2026-09-13) pushFeed({ type: 'Released', sponsor: r.ownerName, member: r.memberName, ts: Date.now() }); try { const sc = siteConfig(); const clean = t => String(t || '').replace(/[<>&]/g, ''); if (sc.telegramBotToken && sc.telegramEchoChatId) telegramSend(sc.telegramEchoChatId, '\u{1FAA3} InstantAdPay \u00b7 ' + clean(r.ownerName) + ' returned ' + clean(r.memberName) + ' to the holding tank', sc.telegramEchoTopicId); } catch (e) {} } return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/tank/contacted' && req.method === 'POST') { // sponsor reached the lead off-site: reset the rescue clock const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await tank.markContacted(s.email, b.email); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/gift' && req.method === 'POST') { // PIF: log a wallet-to-wallet POL gift and tell the recipient const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await tank.recordGift(s.email, b.email, b.tx, b.pol); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/tank' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await tank.adminView()); } // -- admin Traffic tab: page views + join-page views + signups + $20 buyers, by referring // domain / first-touch source, by landing page, by angle and by day (Marty, 2026-09-12) if (p === '/api/admin/traffic' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const days = Math.min(365, Math.max(1, Number(u.searchParams.get('days')) || 30)); const since = Date.now() - days * 86400000, sinceDay = new Date(since).toISOString().slice(0, 10); const hosts = {}, paths = {}, daily = {}, angles = {}; const H = k => (hosts[k] = hosts[k] || { source: k, hits: 0, joinViews: 0, signups: 0, registered: 0, buyers: 0 }); const Dy = k => (daily[k] = daily[k] || { day: k, hits: 0, signups: 0 }); for (const r of await traffic.rows(sinceDay)) { H(r.host).hits += r.n; paths[r.path] = (paths[r.path] || 0) + r.n; Dy(r.day).hits += r.n; } for (const v of await coach.viewsSince(since)) { H(v.ref || 'direct').joinViews += 1; const a = angles[v.angle || 'plain'] = angles[v.angle || 'plain'] || { angle: v.angle || 'plain', views: 0, signups: 0 }; a.views += 1; } const buyerIds = new Set(); for (const ev of chain.recentEvents(1e9)) if (ev.type === 'BuyerCounted' && ev.newBuyerId) buyerIds.add(ev.newBuyerId); for (const a of await accounts.listAll(5000)) { if (!a.created || a.created < since) continue; const h = H(a.joinedRef || 'direct'); h.signups += 1; if (a.memberId) h.registered += 1; if (a.memberId && buyerIds.has(a.memberId)) h.buyers += 1; Dy(new Date(a.created).toISOString().slice(0, 10)).signups += 1; const k = a.joinedVia || 'plain'; angles[k] = angles[k] || { angle: k, views: 0, signups: 0 }; angles[k].signups += 1; } const sources = Object.values(hosts).sort((x, y) => (y.hits + y.joinViews + y.signups * 10) - (x.hits + x.joinViews + x.signups * 10)); return json(res, 200, { days, sources, paths: Object.entries(paths).map(([path, hits]) => ({ path, hits })).sort((x, y) => y.hits - x.hits), angles: Object.values(angles).sort((x, y) => y.views - x.views), daily: Object.values(daily).sort((x, y) => x.day < y.day ? -1 : 1), totals: { hits: sources.reduce((n, s) => n + s.hits, 0), joinViews: sources.reduce((n, s) => n + s.joinViews, 0), signups: sources.reduce((n, s) => n + s.signups, 0), buyers: sources.reduce((n, s) => n + s.buyers, 0) } }); } if (p === '/api/my/coach' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const cv = await coach.coachView(s.email); for (const d of cv.directs) if (d.free) { try { d.rescue = await tank.rescueInfo(s.email, await accounts.byEmail(d.email)); } catch (e) {} } // dormant-lead clock return json(res, 200, cv); } // -- link stats: views, joins and buyers per angle link if (p === '/api/my/linkstats' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await coach.linkStats(s.email)); } // -- prospects: the member's own follow-up list // -- Pipeline: the follow-up board (stages from the ledger and the site; notes, follow-ups, tags from the sponsor) if (p === '/api/my/pipeline' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const sc = siteConfig(); if (!pipeline.visible(sc.pipelineMode, s.email, ADMIN_EMAIL)) return json(res, 200, { live: false, eta: sc.pipelineEta || '' }); const b = await pipeline.board(s.email); return json(res, 200, Object.assign({ live: true }, b), { 'Cache-Control': 'no-store' }); } if (p === '/api/my/pipeline/note' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); if (!pipeline.visible(siteConfig().pipelineMode, s.email, ADMIN_EMAIL)) return json(res, 403, { error: 'The Pipeline is not open yet.' }); const r = await pipeline.save(s.email, await readBody(req)); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/prospects' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, { prospects: await coach.prospects(s.email), statuses: coach.STATUSES }); } if (p === '/api/my/prospects' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await coach.saveProspect(s.email, await readBody(req)); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/prospects/remove' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const r = await coach.removeProspect(s.email, b.id); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/profile' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const before = await accounts.byEmail(s.email); // a username is permanent once set: the invite link, the public wall and every // banner already printed carry it (Marty, 2026-09-10) if (before && before.username && String(b.username || '').trim().toLowerCase() !== String(before.username).toLowerCase()) return json(res, 400, { error: 'Your username is locked. Your invite link, your public page and any banners you shared all carry @' + before.username + '. Contact support if it truly has to change.' }); const r = await accounts.setUsername(s.email, b.username); // First time a username is set (onboarding): now there's a real name to // show, so notify the sponsor here rather than at signup (where it'd just // say "a new member"). Fires once โ€” only on the emptyโ†’set transition. if (!r.error && before && !before.username && b.username) { const acct = await accounts.byEmail(s.email); notifyNewReferral(acct && acct.sponsorRef, acct).catch(() => {}); // everyone's dashboard: "@name just joined" (and whether they are waiting in the tank) if (acct && acct.username) pushFeed({ type: 'Joined', name: '@' + acct.username, tank: !acct.sponsorRef && !acct.memberId, ts: Date.now() }); } return json(res, r.error ? 400 : 200, r); } // -- earn credits by viewing ads (attention-gated daily claim) if (p === '/api/my/earn' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await ads.viewStatus(s.email)); } // fraud-guarded view flow: the server issues a single-use token when it // serves the ad, and only counts the view if the dwell elapsed on the // SERVER clock. Client-side focus tracking pauses the countdown; this is // the floor a script cannot cheat past. if (p === '/api/my/earnview' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const status = await ads.viewStatus(s.email); if (status.views >= status.target || status.claimed) return json(res, 200, { ad: null, status }); const type = String(u.searchParams.get('type') || 'banner'); // members never see (or earn from) their own campaigns in the viewer const ad = await ads.serve(type === 'text' ? 'text' : 'banner', Object.assign({ excludeEmail: s.email }, viewerGeo(req))); if (!ad) return json(res, 200, { ad: null, status }); const token = crypto.randomBytes(16).toString('hex'); // the viewer tab frames the advertiser's REAL url (no click counted for a paid view) earnTokens.set(s.email, { token, ts: Date.now(), adId: ad.id, targetUrl: await ads.targetOf(ad.id), adName: ad.title || ad.name || null }); return json(res, 200, { ad, token, viewUrl: '/view/' + token, status }); } // the viewer tab asks where to point the frame (does not consume the token) if (p === '/api/my/viewinfo' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const t = earnTokens.get(s.email); if (!t || t.token !== String(u.searchParams.get('token') || '')) return json(res, 400, { error: 'That view is no longer open. Head back to the dashboard and load the next ad.' }); return json(res, 200, { targetUrl: t.targetUrl, adName: t.adName || null, dwell: ads.rates().viewDwellSeconds || 10 }); } // human check: handed out only once the dwell has elapsed on the SERVER clock if (p === '/api/my/viewchallenge' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const t = earnTokens.get(s.email); if (!t || t.token !== String(u.searchParams.get('token') || '')) return json(res, 400, { error: 'That view is no longer open.' }); const dwellMs = (ads.rates().viewDwellSeconds || 10) * 1000; const age = Date.now() - t.ts; if (age < dwellMs - 400) return json(res, 200, { early: true, wait: Math.ceil((dwellMs - age) / 1000) }); if (age > 5 * 60 * 1000) { earnTokens.delete(s.email); return json(res, 400, { error: 'That ad went stale. Load a fresh one.' }); } const pick = CAPTCHA.slice().sort(() => Math.random() - 0.5).slice(0, 5); const answer = Math.floor(Math.random() * pick.length); t.challenge = { answer }; return json(res, 200, { prompt: pick[answer][1], options: pick.map(x => x[0]) }); } if (p === '/api/my/adview' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const t = earnTokens.get(s.email); const dwellMs = (ads.rates().viewDwellSeconds || 10) * 1000; if (!t || t.token !== String(b.token || '')) return json(res, 400, { error: 'That view did not check out. Load the next ad and let it finish.' }); const age = Date.now() - t.ts; if (age < dwellMs - 400) return json(res, 400, { error: 'Watch the full ad first.' }); if (age > 5 * 60 * 1000) { earnTokens.delete(s.email); return json(res, 400, { error: 'That ad went stale. Load a fresh one.' }); } // the human check must be solved on the same token if (!t.challenge) return json(res, 400, { error: 'Finish the quick check first.', retry: true }); if (Number(b.answer) !== t.challenge.answer) { t.attempts = (t.attempts || 0) + 1; t.challenge = null; // force a fresh challenge for the next try if (t.attempts >= 3) { earnTokens.delete(s.email); return json(res, 400, { error: 'Three misses โ€” that view is void. Head back and load the next ad.' }); } return json(res, 400, { error: 'Wrong pick.', retry: true }); } earnTokens.delete(s.email); // single use return json(res, 200, await ads.recordView(s.email)); } // -- PolHunter hand-off (2026-09-19): sign the member across; polhunter.com verifies with the shared secret if (p === '/api/my/polhunter' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) { res.writeHead(302, { Location: '/my' }); return res.end(); } const secret = String(process.env.HUNT_SSO_SECRET || '').trim(); const huntUrl = String(process.env.HUNT_URL || 'https://polhunter.com').replace(/\/+$/, ''); if (secret.length < 32) return json(res, 503, { error: 'PolHunter is not connected yet.' }); const acct = await accounts.byEmail(s.email); 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, '_'); // 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/. 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, // where they first arrived from, so PolHunter can pay a bounty only for people it sent joinedRef: (acct && acct.joinedRef) || 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 } if (p === '/api/my/claim' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.claimDaily(s.email); if (!r.error) { try { const paid = await payPromo(s.email, todayCT()); if (paid.length) r.promo = paid; } catch (e) { console.error('promo drip', e.message); } } return json(res, r.error ? 400 : 200, r); } // -- downline lineage: 3 levels, usernames+IDs; email only for directs if (p === '/api/my/line' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const levels = await accounts.downline(s.email, 3); // what each person has paid THIS member so far: sum of TierPaid events where // this member is the recipient and that person is the buyer (all indexed events) // ...counting payouts to every position this account owns (main id + linked positions) const myId = await auth.refreshMemberId(s); const own = (await accounts.positions(s.email)).filter(p => p.memberId); const myIds = new Set([myId, ...own.map(p => p.memberId)].filter(Boolean)); const earnedBy = {}; const qualified = new Set(); // members the contract counted as this account's qualifying buyers ($20+) if (myIds.size) { for (const ev of chain.recentEvents(1e9)) { if (ev.type === 'TierPaid' && myIds.has(ev.recipientId) && ev.buyerId) earnedBy[ev.buyerId] = (BigInt(earnedBy[ev.buyerId] || '0') + BigInt(ev.amountWei)).toString(); if (ev.type === 'BuyerCounted' && myIds.has(ev.sponsorId) && ev.newBuyerId) qualified.add(ev.newBuyerId); } } // who sponsored each person (Marty, 2026-09-14): resolve sponsorRef tokens (username, share code or member id) against the line itself const meAcct = await accounts.byEmail(s.email); const byTok = {}; const label = a => a.username ? '@' + a.username : (a.memberId ? 'member #' + a.memberId : 'member'); for (const a of [meAcct, ...levels.flatMap(L => L.members)].filter(Boolean)) { const n = label(a); if (a.username) byTok[String(a.username).toLowerCase()] = n; if (a.code) byTok[String(a.code).toLowerCase()] = n; if (a.memberId) byTok[String(a.memberId)] = n; } const sponsorOf = m => { const t = String(m.sponsorRef || '').toLowerCase(); if (!t) return null; if (meAcct && (t === String(meAcct.username || '').toLowerCase() || t === String(meAcct.code || '').toLowerCase() || t === String(meAcct.memberId || ''))) return 'you'; return byTok[t] || ('@' + t); }; // a member's linked positions are theirs too: a qualifying buy from one of them lights their chip const posIds = {}; for (const m of levels.flatMap(L => L.members)) { try { posIds[m.email] = (await accounts.positions(m.email)).map(p => p.memberId).filter(Boolean); } catch (e) { posIds[m.email] = []; } } const idsOf = m => [m.memberId, ...(posIds[m.email] || [])].filter(Boolean); // Clinton's ask (2026-09-14): on levels 2 and 3 show who has made their $20+ buy (counted for their own // sponsor) and how many qualifying buyers of their own they have, so a leader can see who is one short const onchain = {}; for (const m of levels.flatMap(L => L.members)) { let bought = false, buyers = 0; for (const id of idsOf(m)) { const mm = await cachedMember(id); if (mm) { if (mm.countedAsBuyer) bought = true; buyers += mm.buyerCount || 0; } } onchain[m.email] = { bought, buyers }; } const out = levels.map(L => ({ level: L.level, members: L.members.map(m => ({ bought: onchain[m.email].bought, buyers: onchain[m.email].buyers, memberId: m.memberId || 0, sponsor: sponsorOf(m), name: m.username ? '@' + m.username : m.memberId ? 'member #' + m.memberId : 'member', ref: m.code || null, // opens the activity drop-down (Clinton's ask, 2026-09-14): any of the three levels email: L.level === 1 ? m.email : null, // directs only joined: m.created, qualified: idsOf(m).some(id => qualified.has(id)), earnedWei: idsOf(m).reduce((n, id) => n + BigInt(earnedBy[id] || '0'), 0n).toString() })) })); // the member's own linked positions sit on level 1 too, labelled as theirs if (own.length) { if (!out.find(L => L.level === 1)) out.unshift({ level: 1, members: [] }); const L1 = out.find(L => L.level === 1); own.forEach((p, i) => L1.members.push({ memberId: p.memberId, name: 'You ยท position ' + (i + 2), own: true, email: null, joined: p.created, earnedWei: earnedBy[p.memberId] || '0' })); } return json(res, 200, { levels: out, counts: out.map(L => L.members.length) }); } // -- who is working: one downline member's activity, any of the three levels (Clinton, 2026-09-14) if (p === '/api/my/line/activity' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const ref = String(u.searchParams.get('ref') || '').trim().toLowerCase().slice(0, 40); const a = ref ? await accounts.byCode(ref) : null; if (!a || !(await accounts.isDownlineOf(s.email, a.email, 3))) return json(res, 404, { error: 'Not in your line.' }); const now = Date.now(); const out = { name: a.username ? '@' + a.username : (a.memberId ? 'member #' + a.memberId : 'member'), joined: a.created, lastSeen: a.lastSeen || 0, quietDays: Math.floor((now - Math.max(a.created || 0, a.lastSeen || 0)) / 86400000), wallet: !!a.address, memberId: a.memberId || 0 }; try { const c = await coach.describe(a, now); out.rung = c.label; out.next = c.next; out.stalled = c.stalled; out.buyerCount = c.buyerCount || 0; } catch (e) {} try { const v = await ads.viewStatus(a.email); out.viewsToday = v.views; out.target = v.target; out.claimedToday = v.claimed; out.streakDay = v.claimed ? v.streakDay : Math.max(0, v.streakDay - 1); } catch (e) {} try { const cs = await ads.listCampaigns(a.email); out.campaigns = cs.length; out.campaignsActive = cs.filter(c => c.status === 'active').length; out.imps = cs.reduce((n, c) => n + (c.imps || 0) + (c.impsNas || 0), 0); } catch (e) {} try { const ls = await coach.linkStats(a.email); out.linkViews30 = (ls.angles || []).reduce((n, r) => n + (r.views30 || 0), 0); out.linkViews = ls.totalViews || 0; out.joins = ls.totalJoins || 0; } catch (e) {} try { out.directs = ((await accounts.downline(a.email, 1))[0] || { members: [] }).members.length; } catch (e) { out.directs = 0; } try { out.badges = await ads.milestonesOf(a.email); } catch (e) { out.badges = []; } try { out.positions = (await accounts.positions(a.email)).length; } catch (e) { out.positions = 0; } return json(res, 200, out); } // -- broadcast a message to your downline (1/day), on-site inbox + email if (p === '/api/my/broadcast' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const subject = String(b.subject || '').trim().slice(0, 160); const body = ads.sanitizeRich(b.body); const plain = body.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim(); if (!subject) return json(res, 400, { error: 'Give your message a subject.' }); if (plain.length < 10) return json(res, 400, { error: 'Write a message first.' }); const last = await messages.lastBroadcastAt(s.email); if (Date.now() - last < 24 * 3600 * 1000) return json(res, 429, { error: 'You can send one broadcast a day. Try again in ' + Math.ceil((24 * 3600 * 1000 - (Date.now() - last)) / 3600000) + 'h.' }); const depth = b.scope === 'direct' ? 1 : 3; const levels = await accounts.downline(s.email, depth); const recipients = [...new Set(levels.flatMap(L => L.members.map(m => m.email)).filter(Boolean))]; if (!recipients.length) return json(res, 400, { error: 'No one in your line to message yet.' }); const memberId = s.memberId || await auth.refreshMemberId(s); await messages.deliver(memberId, s.email, recipients, subject, body); // email each recipient too (best-effort; never blocks the on-site delivery) if (mailer.hasKey()) { const who = (await accounts.byEmail(s.email)); const from = who && who.username ? '@' + who.username : 'your sponsor'; for (const to of recipients) { mailer.send(to, 'Message from ' + from + ': ' + subject, plain + '\n\nโ€” sent via your InstantAdPay upline. Read it in your dashboard: https://instantadpay.com/my#line') .catch(() => {}); } } return json(res, 200, { ok: true, sent: recipients.length }); } // -- sponsor messages: this member's inbox from their upline if (p === '/api/my/messages' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const items = await messages.inbox(s.email); const names = await accounts.namesForMembers([...new Set(items.map(i => i.fromMember).filter(Boolean))]); for (const i of items) i.fromName = (i.fromMember && names[i.fromMember]) ? '@' + names[i.fromMember] : i.fromMember ? 'member #' + i.fromMember : 'your upline'; return json(res, 200, { items, unread: items.filter(i => !i.read).length }); } m = /^\/api\/my\/messages\/(\d+)\/read$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await messages.markRead(s.email, m[1])); } // โ”€โ”€ SPONSOR CHAT (two-way): presence-aware 1:1 threads up/down the line โ”€โ”€ const chatOnline = ts => (Date.now() - (ts || 0)) < 60000; const chatName = a => !a ? 'member' : (a.username ? '@' + a.username : (a.memberId ? 'member #' + a.memberId : 'member')); // lightweight presence heartbeat (called on a timer while the dashboard is open) if (p === '/api/my/ping' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 200, { ok: true, chatUnread: 0 }); accounts.touchSeen(s.email).catch(() => {}); return json(res, 200, { ok: true, chatUnread: await messages.chatUnread(s.email) }); } // training center content (admin-curated via data/training.json) if (p === '/api/training' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s) return json(res, 401, { error: 'Sign in first.' }); let items = []; try { const j = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'training.json'), 'utf8')); if (Array.isArray(j)) items = j; } catch (e) {} // a card with feature:'pipeline' waits for the switch, so the video can be ready before the tab opens (Marty, 2026-09-15) const scT = siteConfig(); const launched = scT.launchAt && Date.now() >= new Date(scT.launchAt).getTime(); // feature:'polhunter' waits for the launch moment (Marty, 2026-09-20) items = items.filter(it => !it.feature || (it.feature === 'pipeline' && pipeline.visible(scT.pipelineMode, s.email, ADMIN_EMAIL)) || (it.feature === 'polhunter' && launched)); if (!items.length) items = [{ title: 'Getting started with InstantAdPay', desc: 'How the platform works, how every payout splits on-chain to real wallets, and how to build your line.', docUrl: 'https://instantadpay.com/' }]; return json(res, 200, { items }); } // daily login bonus (once/day, gentle streak) โ€” granted after the login flow if (p === '/api/my/login-bonus' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, await ads.grantLoginBonus(s.email)); } // -- rehearsal test-POL faucet: top a connected wallet up to 10 test-POL // (anvil_setBalance, no key needed). Rehearsal-only, rate-limited. if (p === '/api/my/faucet' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s) return json(res, 401, { error: 'Sign in first.' }); if (!siteConfig().rehearsal) return json(res, 400, { error: 'The faucet is only open during the rehearsal.' }); const b = await readBody(req); const addr = String(b.address || '').trim().toLowerCase(); if (!/^0x[0-9a-f]{40}$/.test(addr)) return json(res, 400, { error: 'Connect your wallet first.' }); // Amoy is a public testnet: testers fund their own connected wallet from // the public Amoy faucet (no server-minted balance). We just echo the // address + faucet link; the client copies the address and opens it. let balHex = '0x0'; try { balHex = await chain.rpc('eth_getBalance', [addr, 'latest']); } catch (e) {} return json(res, 200, { ok: true, faucetUrl: 'https://faucet.polygon.technology/', address: addr, balanceWei: BigInt(balHex || '0x0').toString() }); } // -- report an ad (auto-approved ads need a safety valve): store + notify admin if (p === '/api/report-ad' && req.method === 'POST') { const b = await readBody(req); if (!Number(b.campaignId)) return json(res, 400, { error: 'Which ad?' }); const s = await auth.fromRequest(req); const who = (s && s.email) || ''; const rec = await reports.add(b.campaignId, who, b.reason, b.note); try { const adminEmail = process.env.ADMIN_EMAIL || ''; // private env only โ€” siteConfig is exposed via /api/config if (adminEmail && mailer.hasKey()) { mailer.send(adminEmail, 'Ad reported on InstantAdPay (campaign #' + rec.campaignId + ')', 'A member flagged an ad.\n\nCampaign: #' + rec.campaignId + '\nReason: ' + rec.reason + '\nReported by: ' + (who || 'anonymous') + '\nNote: ' + (String(b.note || '').slice(0, 500) || '(none)') + '\n\nPause or review it from the admin.').catch(() => {}); } } catch (e) {} return json(res, 200, { ok: true }); } if (p === '/api/my/chat/send' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const me = s.email.toLowerCase(); const b = await readBody(req); const to = String(b.to || '').trim().toLowerCase(); const text = String(b.body || '').replace(/<[^>]*>/g, '').replace(/\s+$/, '').slice(0, 2000).trim(); if (!to || to === me) return json(res, 400, { error: 'Pick who to message.' }); if (!text) return json(res, 400, { error: 'Write a message first.' }); const target = await accounts.byEmail(to); if (!target) return json(res, 404, { error: 'No such member.' }); // authorize: existing thread, my direct sponsor, or someone in my downline let ok = (await messages.thread(me, to, 0, 1)).length > 0; if (!ok) { const spon = await accounts.sponsorOf(me); ok = !!(spon && spon.email && spon.email.toLowerCase() === to); } if (!ok) ok = await accounts.isDownlineOf(me, to); if (!ok) return json(res, 403, { error: 'You can only message your direct sponsor or someone in your line.' }); if ((await accounts.getMutes(to)).map(x => String(x).toLowerCase()).includes(me)) return json(res, 403, { error: 'They are not accepting messages from you right now.' }); const memberId = s.memberId || await auth.refreshMemberId(s); const msg = await messages.sendChat(memberId, me, to, text); // email only when they are offline AND I have not messaged them in ~10 min (no mid-chat spam) try { if (mailer.hasKey() && !chatOnline(target.lastSeen)) { const mine = (await messages.thread(me, to, 0, 400)).filter(x => x.id !== msg.id && String(x.fromEmail).toLowerCase() === me); const lastMineTs = mine.length ? mine[mine.length - 1].sent : 0; if (Date.now() - lastMineTs > 10 * 60 * 1000) { const who = await accounts.byEmail(me); const from = who && who.username ? '@' + who.username : 'someone in your InstantAdPay line'; mailer.send(to, 'New message from ' + from, text.slice(0, 400) + '\n\nโ€” reply in your dashboard: https://instantadpay.com/my').catch(() => {}); } } } catch (e) {} return json(res, 200, { ok: true, message: { id: msg.id, sent: msg.sent, fromMe: true, body: text } }); } if (p === '/api/my/chat/thread' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const me = s.email.toLowerCase(); const other = String(u.searchParams.get('with') || '').trim().toLowerCase(); const after = Number(u.searchParams.get('after')) || 0; if (!other) return json(res, 400, { error: 'Who with?' }); // may view a thread I'm party to (existing), or one I'm allowed to start let ok = (await messages.thread(me, other, 0, 1)).length > 0; if (!ok) { const spon = await accounts.sponsorOf(me); ok = !!(spon && spon.email && spon.email.toLowerCase() === other); } if (!ok) ok = await accounts.isDownlineOf(me, other); if (!ok) return json(res, 403, { error: 'Not your conversation.' }); const msgs = await messages.thread(me, other, after, 300); await messages.markChatRead(me, other); accounts.touchSeen(me).catch(() => {}); const oa = await accounts.byEmail(other); const iMute = (await accounts.getMutes(me)).map(x => String(x).toLowerCase()).includes(other); const theyMuteMe = (await accounts.getMutes(other)).map(x => String(x).toLowerCase()).includes(me); return json(res, 200, { messages: msgs.map(x => ({ id: x.id, fromMe: String(x.fromEmail).toLowerCase() === me, body: x.body, sent: x.sent })), otherName: chatName(oa), online: oa ? chatOnline(oa.lastSeen) : false, available: oa ? oa.chatAvailable !== false : true, iMute, blocked: theyMuteMe, canMute: await accounts.isDownlineOf(me, other) }); } if (p === '/api/my/chat/threads' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const list = await messages.threadList(s.email.toLowerCase()); for (const t of list) { const a = await accounts.byEmail(t.email); t.name = chatName(a); t.online = a ? chatOnline(a.lastSeen) : false; } list.sort((x, y) => (y.last.sent || 0) - (x.last.sent || 0)); const meAcct = await accounts.byEmail(s.email); return json(res, 200, { threads: list, available: meAcct ? meAcct.chatAvailable !== false : true }); } if (p === '/api/my/chat/available' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); return json(res, 200, await accounts.setChatAvailable(s.email, !!b.available)); } if (p === '/api/my/chat/mute' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const target = String(b.email || '').trim().toLowerCase(); if (!target) return json(res, 400, { error: 'Who?' }); const allowed = (await accounts.isDownlineOf(s.email, target)) || (await messages.thread(s.email.toLowerCase(), target, 0, 1)).length > 0; if (!allowed) return json(res, 403, { error: 'You can only mute someone in your line.' }); return json(res, 200, await accounts.setMute(s.email, target, !!b.muted)); } // -- featured rotation: the live featured links + dilution stats if (p === '/api/featured' && req.method === 'GET') { const items = await ads.serveFeatured(viewerGeo(req)); try { const sv = await auth.fromRequest(req); ads.noteFeaturedViews(items, (sv && sv.email) || clientIp(req)).catch(() => {}); } catch (e) {} // count the view (per viewer per hour) const names = await accounts.namesForMembers([...new Set(items.map(i => i.memberId).filter(Boolean))]); for (const i of items) i.by = (i.memberId && names[i.memberId]) ? '@' + names[i.memberId] : (i.memberId ? 'member #' + i.memberId : null); return json(res, 200, { items }); } if (p === '/api/featured/stats' && req.method === 'GET') { return json(res, 200, await ads.featuredStats()); } // -- QR code (SVG) for any InstantAdPay URL โ€” used on bio/wall pages if (p === '/api/qr' && req.method === 'GET') { const data = String(u.searchParams.get('d') || '').slice(0, 300); if (!QR || !data) { res.writeHead(404, baseHeaders()); return res.end(); } try { const svg = await QR.toString(data, { type: 'svg', margin: 1, width: 240, color: { dark: '#0e7d5f', light: '#f2fbf8' } }); // mint-green modules, still high-contrast for reliable scanning res.writeHead(200, baseHeaders({ 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=3600' })); return res.end(svg); } catch (e) { res.writeHead(500, baseHeaders()); return res.end(); } } // -- profile: avatar + bio if (p === '/api/my/profile-details' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const avatar = b.avatarUrl === undefined ? undefined : String(b.avatarUrl || '').trim(); if (avatar && !/^(\/uploads\/[a-z0-9]{24}\.(png|jpg|webp|gif)|https:\/\/[^\s]+)$/i.test(avatar)) return json(res, 400, { error: 'Avatar must be an uploaded image or an https image URL.' }); const bio = b.bio === undefined ? undefined : String(b.bio || '').trim().slice(0, 600); // socials: {platform: url}; keep only known platforms with valid https urls let socials; if (b.socials !== undefined) { const PLAT = ['facebook', 'twitter', 'youtube', 'instagram', 'tiktok', 'telegram', 'linkedin', 'website', 'video']; const clean = {}; for (const p of PLAT) { const v = String((b.socials && b.socials[p]) || '').trim(); if (v && /^https:\/\/[^\s]+$/i.test(v) && v.length <= 300) clean[p] = v; } // intro video on the public wall: a YouTube, Vimeo or direct .mp4/.webm link only if (clean.video && !/^https:\/\/((www\.|m\.)?youtube\.com\/(watch\?|shorts\/|embed\/)|youtu\.be\/|(www\.)?vimeo\.com\/\d+|player\.vimeo\.com\/video\/\d+|[^\s]+\.(mp4|webm)(\?|$))/i.test(clean.video)) return json(res, 400, { error: 'The intro video needs to be a YouTube link, a Vimeo link, or a direct .mp4 link.' }); socials = Object.keys(clean).length ? JSON.stringify(clean) : null; } const r = await accounts.setProfile(s.email, avatar, bio, socials); return json(res, r.error ? 400 : 200, r); } // -- wall positions 2 & 3: the member's own offers, unlocked at 2 / 5 qualifying buyers if (p === '/api/my/wall-offers' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const src = Array.isArray(b.offers) ? b.offers.slice(0, 2) : []; const out = []; for (let i = 0; i < 2; i++) { const o = src[i] || {}; const targetUrl = String(o.targetUrl || '').trim(); const bannerUrl = String(o.bannerUrl || '').trim(); const title = String(o.title || '').trim().slice(0, 60); if (targetUrl && !/^https:\/\/[^\s]+$/i.test(targetUrl)) return json(res, 400, { error: 'Position ' + (i + 2) + ': the link must start with https://' }); if (bannerUrl && !/^(\/uploads\/[a-z0-9]{24}\.(png|jpg|webp|gif)|https:\/\/[^\s]+)$/i.test(bannerUrl)) return json(res, 400, { error: 'Position ' + (i + 2) + ': banner must be an uploaded image or an https image URL.' }); out.push(targetUrl ? { title: title || null, bannerUrl: bannerUrl || null, targetUrl } : null); } const r = await accounts.setWallOffers(s.email, out.some(Boolean) ? JSON.stringify(out) : null); return json(res, r.error ? 400 : 200, r); } // -- line banner: the member's viral slot on welcome tours + their wall if (p === '/api/my/linebanner' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const target = String(b.targetUrl || '').trim(); if (!/^https?:\/\/[^\s]+$/i.test(target)) return json(res, 400, { error: 'Destination URL must start with http(s)://' }); const fc = await frameCheck(target); // welcome tours frame it full screen if (!fc.ok) return json(res, 400, { error: fc.reason }); const banner = String(b.bannerUrl || '').trim(); if (banner && !/^(\/uploads\/[a-z0-9]{24}\.(png|jpg|webp|gif)|https:\/\/[^\s]+)$/i.test(banner)) return json(res, 400, { error: 'Banner must be an uploaded image or an https image URL.' }); const r = await accounts.setLineBanner(s.email, banner || null, target); return json(res, r.error ? 400 : 200, r); } // -- welcome tour (gauntlet): meet the 3-level upline, then unlock welcome credits if (p === '/api/my/gauntlet' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); if (await ads.welcomeGranted(s.email)) return json(res, 200, { pending: false }); const slides = (await uplineSlides(s.email)).filter(a => a.lineTargetUrl) .map((a, i) => ({ name: a.username ? '@' + a.username : a.memberId ? 'member #' + a.memberId : 'a member', bannerUrl: a.lineBannerUrl || null, targetUrl: a.lineTargetUrl })); if (!slides.length) return json(res, 200, { pending: false }); const token = crypto.randomBytes(16).toString('hex'); gauntletTokens.set(s.email, { token, ts: Date.now(), n: slides.length }); return json(res, 200, { pending: true, slides, dwell: 10, token }); } if (p === '/api/my/gauntlet/complete' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const t = gauntletTokens.get(s.email); if (!t || t.token !== String(b.token || '')) return json(res, 400, { error: 'That tour is no longer open. Reload and try again.' }); if (Date.now() - t.ts < t.n * 10 * 1000 - 1500) return json(res, 400, { error: 'Give each site its ten seconds first.' }); gauntletTokens.delete(s.email); await ads.grantWelcome(s.email); return json(res, 200, { ok: true, credited: ads.rates().welcomeCredits || 0 }); } // -- public banner wall m = /^\/api\/wall\/([A-Za-z0-9_]{1,20})$/.exec(p); if (m && req.method === 'GET') { const tok = m[1].toLowerCase(); let a = await accounts.byUsername(tok); if (!a) a = await accounts.byCode(tok); if (!a) return json(res, 404, { error: 'No wall under that name.' }); const ownName = a.username ? '@' + a.username : a.memberId ? 'member #' + a.memberId : 'a member'; let bc = 0; if (a.memberId) { try { bc = (await chain.member(a.memberId)).buyerCount || 0; } catch (e) {} } const unlocked = wallUnlockedFor(bc); const offers = parseWallOffers(a); // uplines with a live banner, in order (an upline with nothing set is skipped, not shown empty) const ups = (await uplineSlides(a.email, 2)).filter(x => x.lineTargetUrl) .map(x => ({ name: x.username ? '@' + x.username : x.memberId ? 'member #' + x.memberId : 'a member', bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl, upline: true })); const adAds = getAdminWallAds(); let ai = 0; const houseAd = () => { if (!adAds.length) return null; const ad = adAds[ai++ % adAds.length]; return { name: ad.name || 'InstantAdPay', bannerUrl: ad.bannerUrl || null, targetUrl: ad.targetUrl || 'https://instantadpay.com/', admin: true }; }; const ladder = [{ name: ownName, bannerUrl: a.lineBannerUrl || null, targetUrl: a.lineTargetUrl || null, own: true }]; for (let i = 1; i < 3; i++) { const o = offers[i - 1]; if (i < unlocked && o && o.targetUrl) { ladder.push({ name: (o.title || ownName), bannerUrl: o.bannerUrl || null, targetUrl: o.targetUrl, own: true }); continue; } const u = ups.shift(); if (u) { ladder.push(u); continue; } const h = houseAd(); if (h) ladder.push(h); } const finalLadder = ladder.slice(0, 3); // the wall owner's achievement badge (their highest reached tier) let badge = null; if (a.memberId) { const t = bc >= 5 ? ['nexus', 'Nexus'] : bc >= 2 ? ['circuit', 'Circuit'] : bc >= 1 ? ['surge', 'Surge'] : ['spark', 'Spark']; badge = { img: '/badges/badge-' + t[0] + '.jpg?v=2', label: t[1] }; } const joinPath = '/join/' + (a.username || a.code); let socials = null; try { socials = a.socials ? JSON.parse(a.socials) : null; } catch (e) {} return json(res, 200, { name: a.username ? '@' + a.username : 'member #' + (a.memberId || 0), avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials, badge, joinUrl: joinPath, qrUrl: '/api/qr?d=' + encodeURIComponent('https://instantadpay.com' + joinPath), ladder: finalLadder, unlocked, buyerCount: bc }); } // -- watch-to-earn video ads: serve one, then reward a server-clock-verified watch if (p === '/api/my/videos' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const status = await ads.videoStatus(s.email); if (status.left <= 0) return json(res, 200, { ad: null, status }); const orientation = String(u.searchParams.get('orientation') || ''); // 'portrait' = Shorts reel, 'landscape' = Watch videos tab const ad = await ads.serveVideo(Object.assign({ excludeEmail: s.email, orientation }, viewerGeo(req))); // never your own video if (!ad) { // distinguish "you have watched every live video today" from "nothing is live" (Marty, 2026-09-13) let allWatched = false; try { allWatched = !!(await ads.serveVideo(Object.assign({ excludeEmail: s.email, orientation, ignoreSeen: true }, viewerGeo(req)))); } catch (e) {} // this surface is empty but the other one may not be: the Watch tab holds the landscape // videos and Shorts holds the portrait ones, and they share one daily cap, so a member // who runs the tab dry still has clips waiting (Marty, 2026-09-22) let otherFormat = null; if (status.left > 0 && (orientation === 'landscape' || orientation === 'portrait')) { const alt = orientation === 'landscape' ? 'portrait' : 'landscape'; try { if (await ads.serveVideo(Object.assign({ excludeEmail: s.email, orientation: alt }, viewerGeo(req)))) otherFormat = alt === 'portrait' ? 'shorts' : 'watch'; } catch (e) {} } return json(res, 200, { ad: null, status, allWatched, otherFormat }); } const token = crypto.randomBytes(16).toString('hex'); videoTokens.set(s.email, { token, ts: Date.now(), id: ad.id, secs: ad.watchSecs }); return json(res, 200, { ad, token, status }); } if (p === '/api/my/videowatch' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const t = videoTokens.get(s.email); if (!t || t.token !== String(b.token || '')) return json(res, 400, { error: 'That video is no longer open. Load the next one.' }); const age = Date.now() - t.ts; if (age < t.secs * 1000 - 600) return json(res, 400, { error: 'Watch the full video first.' }); if (age > t.secs * 1000 + 10 * 60 * 1000) { videoTokens.delete(s.email); return json(res, 400, { error: 'That watch went stale. Load a fresh video.' }); } videoTokens.delete(s.email); // single use if (await ads.hasWatchedVideoToday(s.email, t.id)) // once-per-day-per-video: no double earning return json(res, 200, { ok: true, credited: 0, status: await ads.videoStatus(s.email), already: true }); const tier = await ads.chargeVideoView(t.id); // charge advertiser; null if it ran dry if (!tier) return json(res, 200, { ok: true, credited: 0, status: await ads.videoStatus(s.email), gone: true }); await ads.addEarned(s.email, tier.reward, { log: { kind: 'earn', note: 'Watched a video (' + tier.secs + 's)' } }); await ads.markVideoSeen(s.email, t.id); const status = await ads.recordVideoWatch(s.email); return json(res, 200, { ok: true, credited: tier.reward, status }); } // -- verified visits: view a member's site (new tab) for the dwell, pass a // human check, and it counts as one guaranteed unique visit for the pack if (p === '/api/my/visits' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const status = await ads.visitStatus(s.email); if (status.count >= status.cap) return json(res, 200, { ad: null, status }); const ad = await ads.serveVisit(s.email, viewerGeo(req)); if (!ad) return json(res, 200, { ad: null, status }); const token = crypto.randomBytes(16).toString('hex'); visitTokens.set(s.email, { token, ts: Date.now(), id: ad.id }); return json(res, 200, { ad, token, status }); } if (p === '/api/my/visitchallenge' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const t = visitTokens.get(s.email); if (!t || t.token !== String(u.searchParams.get('token') || '')) return json(res, 400, { error: 'That visit is no longer open.' }); const dwellMs = (ads.rates().visitDwellSeconds || 8) * 1000; const age = Date.now() - t.ts; if (age < dwellMs - 400) return json(res, 200, { early: true, wait: Math.ceil((dwellMs - age) / 1000) }); if (age > 5 * 60 * 1000) { visitTokens.delete(s.email); return json(res, 400, { error: 'That visit went stale. Load a fresh one.' }); } const pick = CAPTCHA.slice().sort(() => Math.random() - 0.5).slice(0, 5); const answer = Math.floor(Math.random() * pick.length); t.challenge = { answer }; return json(res, 200, { prompt: pick[answer][1], options: pick.map(x => x[0]) }); } if (p === '/api/my/visitdone' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const b = await readBody(req); const t = visitTokens.get(s.email); if (!t || t.token !== String(b.token || '')) return json(res, 400, { error: 'That visit did not check out. Load the next one.' }); const dwellMs = (ads.rates().visitDwellSeconds || 8) * 1000; if (Date.now() - t.ts < dwellMs - 400) return json(res, 400, { error: 'Give the site the full visit first.' }); if (!t.challenge) return json(res, 400, { error: 'Finish the quick check first.', retry: true }); if (Number(b.answer) !== t.challenge.answer) { t.challenge = null; return json(res, 400, { error: 'Wrong pick.', retry: true }); } visitTokens.delete(s.email); const r = await ads.completeVisit(s.email, t.id); if (r.error) return json(res, 400, r); return json(res, 200, Object.assign(r, { status: await ads.visitStatus(s.email) })); } // -- onsite solo ads: member inbox with read rewards if (p === '/api/my/inbox' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.inboxList(s.email, viewerGeo(req)); const names = await accounts.namesForMembers([...new Set(r.items.map(i => i.fromMemberId).filter(Boolean))]); for (const i of r.items) i.fromName = (i.fromMemberId && names[i.fromMemberId]) ? '@' + names[i.fromMemberId] : i.fromMemberId ? 'member #' + i.fromMemberId : 'a member'; return json(res, 200, r); } m = /^\/api\/my\/inbox\/(\d+)$/.exec(p); if (m && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.inboxOpen(s.email, m[1]); if (!r.error) { const names = r.fromMemberId ? await accounts.namesForMembers([r.fromMemberId]) : {}; r.fromName = (r.fromMemberId && names[r.fromMemberId]) ? '@' + names[r.fromMemberId] : r.fromMemberId ? 'member #' + r.fromMemberId : 'a member'; } return json(res, r.error ? 404 : 200, r); } // media upload for solo ads: raw body, size-capped, magic-byte verified if (p === '/api/my/upload' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return handleUpload(req, res, s.email); } m = /^\/api\/my\/inbox\/(\d+)\/visit$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.markSoloVisit(s.email, m[1]); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/my\/inbox\/(\d+)\/claim$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.claimSoloRead(s.email, m[1]); return json(res, r.error ? 400 : 200, r); } if (p === '/api/my/activity' && req.method === 'GET') { const s = await 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(1e9); // whole history (was the last 600 events: older members saw three empty boxes) return json(res, 200, { memberId: id, earnings: await attachNames(evs.filter(e => (e.type === 'TierPaid' && e.recipientId === id) || (e.type === 'AwardPaid' && e.toId === id)).slice(0, 300)), purchases: await attachNames(evs.filter(e => e.type === 'Purchase' && e.buyerId === id).slice(0, 300)), referrals: await attachNames(evs.filter(e => (e.type === 'MemberActivated' && e.sponsorId === id) || (e.type === 'BuyerCounted' && e.sponsorId === id)).slice(0, 300)) }); } // -- ad engine (spec ยง8b v1: banners, text, login ads) if (p === '/api/ads/slot' && req.method === 'GET') { const t = String(u.searchParams.get('type') || 'banner'); const ad = await ads.serve(t, Object.assign({ width: Number(u.searchParams.get('w')) || 0, height: Number(u.searchParams.get('h')) || 0 }, viewerGeo(req))); // login ads: the member clicks "Open Ad" (a real, counted click into a // new tab) while the countdown runs on our interstitial page if (ad && t === 'login') ad.dwell = ads.rates().loginDwellSeconds || 10; return json(res, 200, { ad }); } m = /^\/api\/ads\/click\/(\d+)$/.exec(p); if (m && req.method === 'GET') { const target = await ads.click(m[1]); if (!target) { res.writeHead(404, baseHeaders()); return res.end(); } coach.recordClick(m[1], req.headers.referer); // where the click happened res.writeHead(302, baseHeaders({ Location: target })); return res.end(); } if (p === '/api/my/trace' && req.method === 'GET') { // payment trace for yourself or anyone within 3 levels of you, either direction const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const mine = (await myMemberIds(s)).ids.filter(Boolean); const who = String(u.searchParams.get('who') || '').trim().replace(/^[@#]/, '').toLowerCase(); let target = /^\d+$/.test(who) ? Number(who) : 0; if (!target && who) { let a = await accounts.byUsername(who); if (!a) a = await accounts.byCode(who); if (a && a.memberId) target = a.memberId; } if (!target && !who && mine.length) target = mine[0]; if (!target) return json(res, 400, { error: who ? 'No activated member called "' + who.slice(0, 40) + '". Use a member number or a username of someone who has bought a package.' : 'Enter a member number or username.' }); if (!mine.length) return json(res, 403, { error: 'Link a wallet and buy a package first; the trace works on chain positions.' }); const smap = sponsorMapFromIndex(chain.recentEvents(1e9)); const ok = mine.includes(target) || uplineIds(smap, target, 3).some(x => mine.includes(x)) || mine.some(m => uplineIds(smap, m, 3).includes(target)); if (!ok) return json(res, 403, { error: 'You can trace yourself, anyone up to 3 levels below you, and your own 3 uplines.' }); return json(res, 200, await tracePurchases(target, 30)); } if (p === '/api/my/credits/activity' && req.method === 'GET') { // credit ledger: every earn and spend with a reason, newest first const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); return json(res, 200, { items: await ads.creditActivity(s.email, 40) }); } if (p === '/api/my/campaigns' && req.method === 'GET') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const memberId = await auth.refreshMemberId(s); const all = await ads.listCampaigns(s.email); // archived ones keep their full stats but leave the working list (Marty, 2026-09-23) const out = { campaigns: all.filter(c => c.status !== 'archived'), archived: all.filter(c => c.status === 'archived'), rates: ads.rates(), bannerSizes: ads.bannerSizes() }; const ids = all.map(c => c.id); // archived included: their numbers are the whole point of keeping them out.clickSources = await coach.clickSources(ids); out.hours = await ads.hoursFor(ids); // on-site views per UTC hour, last 7 days out.geo = await ads.geoFor(ids); // on-site serves per viewer country { const t = geo.tierLists(siteConfig()); out.tiers = { t1: [...t.t1], t2: [...t.t2] }; out.geoReady = geo.status().loaded; } const pool = await ads.balances((await myMemberIds(s)).ids, s.email); out.purchasedCredits = pool.total; out.largestPosition = pool.best.avail; // a single campaign budget has to fit one position out.positionCount = pool.per.length; out.creditedCredits = pool.credited; // refunded/credited purchased money, free (counts inside purchasedCredits) out.earnedCredits = pool.earned; // earned pool minus what live campaigns already hold out.earnedReserved = pool.earnedReserved; out.inCampaigns = pool.inCampaigns; // budget still to deliver across live campaigns out.availableCredits = pool.available; return json(res, 200, out); } if (p === '/api/my/campaigns' && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const memberId = await auth.refreshMemberId(s); // 0 is fine: earned credits fund banner/text const b = await readBody(req); if (!['login', 'solo', 'video', 'featured'].includes(String(b.type || ''))) { // banner/text surf views frame the target; login/video/solo/featured open in a new tab or play in our own player if (b.type === 'banner' || (b.type === 'login' && b.imageUrl)) { const ic = await imageCheck(b.imageUrl); if (!ic.ok) return json(res, 400, { error: ic.reason }); } const fc = await frameCheck(b.targetUrl); if (!fc.ok) return json(res, 400, { error: fc.reason }); } if (String(b.type) === 'video') { const vc = await videoCheck(b.videoUrl); if (!vc.ok) return json(res, 400, { error: vc.reason }); } // charge the best-funded of the member's positions (main + Qualified Start // wallets). A campaign burns from one member id, so the budget must fit inside it. const ids = (await myMemberIds(s)).ids; const pool = await ads.balances(ids, s.email); const fundId = pool.best.memberId || memberId; const earnedNow = String(b.type) !== 'login' ? pool.earned : 0; const budget = Math.floor(Number(b.budget) || 0); if (pool.per.length > 1 && budget > pool.best.avail + pool.credited + earnedNow && budget <= pool.total + earnedNow) return json(res, 400, { error: 'Your credits are spread across ' + pool.per.length + ' positions and one campaign spends from one of them. The largest single position holds ' + pool.best.avail + ' credits: set the budget to that or less, or run two campaigns.' }); const r = await ads.createCampaign(s.email, fundId, b, ids); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/my\/campaigns\/(\d+)\/topup$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const memberId = await auth.refreshMemberId(s); const b = await readBody(req); const r = await ads.topUpCampaign(s.email, memberId, m[1], b.credits); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/my\/campaigns\/(\d+)\/extend$/.exec(p); // featured links: book more days (flat, charged now) if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const memberId = await auth.refreshMemberId(s); const b = await readBody(req); const r = await ads.extendFeatured(s.email, memberId, m[1], b.days); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/my\/campaigns\/(\d+)\/(archive|unarchive)$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = m[2] === 'archive' ? await ads.archive(s.email, m[1]) : await ads.unarchive(s.email, m[1]); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/my\/campaigns\/(\d+)\/(pause|resume)$/.exec(p); if (m && req.method === 'POST') { const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); const r = await ads.setStatus(s.email, m[1], m[2] === 'pause' ? 'paused' : 'active'); return json(res, r.error ? 400 : 200, r); } // -- admin portal: email magic-code sign-in, allowlisted to ADMIN_EMAIL if (p === '/api/admin/auth/start' && req.method === 'POST') { const b = await readBody(req); const e = String(b.email || '').trim().toLowerCase(); if (!ADMIN_EMAIL) return json(res, 503, { error: 'ADMIN_EMAIL is not set on the server.' }); if (!e || e !== ADMIN_EMAIL) return json(res, 403, { error: 'That address is not the admin.' }); const k = 'admin:' + e; const prev = emailCodes.get(k); if (prev && Date.now() < prev.nextAt) return json(res, 429, { error: 'Code already sent. Give it a minute, then try again.' }); const code = String(Math.floor(100000 + Math.random() * 900000)); emailCodes.set(k, { code, exp: Date.now() + 15 * 60 * 1000, tries: 0, nextAt: Date.now() + 60 * 1000 }); if (mailer.hasKey()) { try { await mailer.sendCode(e, code); } catch (err) { console.error('admin sendCode failed', err.message); return json(res, 502, { error: 'Could not send the email. Try again in a minute.' }); } return json(res, 200, { ok: true, sent: true }); } if (!IS_PROD) return json(res, 200, { ok: true, sent: false, devCode: code }); return json(res, 503, { error: 'Email sign-in is not configured yet.' }); } if (p === '/api/admin/auth/verify' && req.method === 'POST') { const b = await readBody(req); const e = String(b.email || '').trim().toLowerCase(); const k = 'admin:' + e; const rec = emailCodes.get(k); if (!rec || rec.exp < Date.now()) return json(res, 400, { error: 'Code expired. Request a fresh one.' }); rec.tries += 1; if (rec.tries > 6) { emailCodes.delete(k); return json(res, 400, { error: 'Too many tries. Request a fresh code.' }); } if (String(b.code || '').trim() !== rec.code) return json(res, 400, { error: 'That code does not match.' }); emailCodes.delete(k); if (e !== ADMIN_EMAIL) return json(res, 403, { error: 'That address is not the admin.' }); const token = mintAdminSession(e); return json(res, 200, { ok: true, email: e }, { 'Set-Cookie': adminCookie(token) }); } if (p === '/api/admin/auth/logout' && req.method === 'POST') { dropAdminSession(req); return json(res, 200, { ok: true }, { 'Set-Cookie': clearAdminCookie() }); } if (p === '/api/admin/me' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 200, { admin: false }); return json(res, 200, { admin: true, email: ADMIN_EMAIL }); } if (p === '/api/admin/refill' && req.method === 'GET') { // who was told their ads ran out, and what they did next if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, refill.stats()); } if (p === '/api/admin/refill/reset' && req.method === 'POST') { // re-decide conversions under a corrected rule if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); return json(res, 200, refill.resetConversions(Array.isArray(b.kinds) ? b.kinds : null)); } if (p === '/api/admin/refill/run' && req.method === 'POST') { // send the next batch now instead of waiting for the tick if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await refill.tick()); } if (p === '/api/admin/overview' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const camps = await ads.adminList(); const byStatus = {}, byType = {}; for (const c of camps) { byStatus[c.status] = (byStatus[c.status] || 0) + 1; byType[c.type] = (byType[c.type] || 0) + 1; } let memberCount = null; try { memberCount = await chain.memberCount(); } catch (e) {} const cc = chain.getConfig(); return json(res, 200, { accounts: await accounts.count(), memberCount, campaigns: camps.length, house: camps.filter(c => c.house).length, byStatus, byType, openReports: await reports.openCount(), pendingBurns: (await ads.pendingBurns()).length, followups: await drip.stats(), chain: { contract: cc.contract, chainId: cc.chainId, chainName: cc.chainName, explorer: cc.explorer }, site: siteConfig(), rates: ads.rates() }); } if (p === '/api/admin/members' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const members = await accounts.listAll(500); // resolve each sponsor token (username, share code or member #) to the sponsor's name // the lookup covers EVERY account, not the page's slice: built from `members` it called every // sponsor who joined before the newest 500 a dead link (144 of them, Marty 2026-09-23) const byTok = {}; for (const m of await accounts.identities()) for (const t of [m.username, m.code, m.memberId ? String(m.memberId) : null]) if (t) byTok[String(t).toLowerCase()] = m; for (const m of members) { const t = String(m.sponsorRef || '').toLowerCase(); const sp = t ? byTok[t] : null; m.sponsorName = sp ? (sp.username ? '@' + sp.username : (sp.memberId ? 'member #' + sp.memberId : sp.email)) : null; m.sponsorVia = sp ? (t === String(sp.username || '').toLowerCase() ? 'username' : t === String(sp.code || '').toLowerCase() ? 'code' : 'member #') : (t ? 'unresolved' : ''); } for (const m of members) { try { const ps = await accounts.positions(m.email); m.positions = ps.length; m.positionIds = ps.map(p => p.memberId).filter(Boolean); } catch (e) { m.positions = 0; } } for (const m of members) { try { const sg = await fraud.get(m.email); m.flags = sg ? sg.flags : []; m.suspended = !!(sg && sg.suspended); m.lastIp = sg ? sg.lastIp : ''; } catch (e) { m.flags = []; m.suspended = false; } } return json(res, 200, { members }); } if (p === '/api/admin/members' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); if (!b.email) return json(res, 400, { error: 'Which member?' }); if (b.suspend !== undefined) { // anti-fraud switch: suspended accounts cannot sign in; flags keep them off the leaderboard and out of adoptions if (b.suspend) await fraud.suspend(b.email, b.reason || 'duplicate account'); else await fraud.unsuspend(b.email); if (b.flags) await fraud.addFlags(b.email, [].concat(b.flags)); return json(res, 200, { ok: true, signals: await fraud.get(b.email) }); } if (b.flags !== undefined) { const f = b.flags === null ? (await fraud.clearFlags(b.email), []) : await fraud.addFlags(b.email, [].concat(b.flags)); return json(res, 200, { ok: true, flags: f }); } const r = await accounts.setSponsorRef(b.email, b.sponsorRef); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/fraud' && req.method === 'GET') { // duplicate signals: shared browsers / IPs, flagged and suspended accounts if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, Object.assign(await fraud.report(), { allow: fraud.allowList(), block: fraud.blockList() })); } // Approved exceptions: people Marty has okayed to hold more than one account. Adding an // address here stops the duplicate checks blocking or hard-flagging them, and stops them // being dropped from the leaderboard or barred from adopting. if (p === '/api/admin/fraud/block' && req.method === 'POST') { const b = await readBody(req); const r = b.remove ? fraud.blockRemove(b.email) : fraud.blockAdd(b.email, b.note, ADMIN_EMAIL || 'admin'); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/fraud/allow' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = b.remove ? fraud.allowRemove(b.email) : fraud.allowAdd(b.email, b.note, ADMIN_EMAIL || 'admin'); if (r.error) return json(res, 400, r); console.log('fraud exception ' + (b.remove ? 'removed' : 'added') + ': ' + String(b.email || '').replace(/^(.).*(@.*)$/, '$1***$2')); return json(res, 200, r); } if (p === '/api/admin/campaigns' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { campaigns: await ads.adminList(), rates: ads.rates(), bannerSizes: ads.bannerSizes(), houseOwner: ads.HOUSE_OWNER }); } if (p === '/api/admin/campaigns' && req.method === 'POST') { // free house ad if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); if (!['login', 'solo', 'video', 'featured'].includes(String(b.type || ''))) { if (b.type === 'banner' || (b.type === 'login' && b.imageUrl)) { const ic = await imageCheck(b.imageUrl); if (!ic.ok) return json(res, 400, { error: ic.reason }); } const fc = await frameCheck(b.targetUrl); if (!fc.ok) return json(res, 400, { error: fc.reason }); } const r = await ads.createHouseCampaign(b); return json(res, r.error ? 400 : 200, r); } m = /^\/api\/admin\/campaigns\/(\d+)\/(pause|resume)$/.exec(p); if (m && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const r = await ads.adminSetStatus(m[1], m[2] === 'pause' ? 'paused' : 'active'); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/audit' && req.method === 'GET') { // counters reconciled against delivery logs if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await audit.run()); } if (p === '/api/admin/sponsor-resolve' && req.method === 'GET') { // support: where would a buy under ?ref= land right now? if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const ref = String(u.searchParams.get('ref') || '').trim(); const detailed = await resolveSponsorDetailed(ref); const walk = detailed.reason === 'notActivated' ? await walkUpActivatedSponsor(ref) : null; return json(res, 200, { ref, detailed, walk, catchId: Number(siteConfig().defaultSponsorId) || 1 }); } if (p === '/api/admin/trace' && req.method === 'GET') { // payment trace for any member if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const who = String(u.searchParams.get('who') || '').trim().replace(/^[@#]/, '').toLowerCase(); let target = /^\d+$/.test(who) ? Number(who) : 0; if (!target && who) { let a = await accounts.byUsername(who); if (!a) a = await accounts.byCode(who); if (!a && who.includes('@')) a = await accounts.byEmail(who); if (a && a.memberId) target = a.memberId; } if (!target) return json(res, 400, { error: 'No activated member matches "' + who.slice(0, 40) + '".' }); return json(res, 200, await tracePurchases(target, 60)); } if (p === '/api/admin/chain/rescan' && req.method === 'POST') { // rebuild the full event history from the deploy block if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await chain.rescan()); } if (p === '/api/admin/chain/status' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { events: chain.eventCount(), historyComplete: chain.historyComplete(), totals: chain.totals() }); } if (p === '/api/admin/reports' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { reports: await reports.list(200) }); } m = /^\/api\/admin\/reports\/(\d+)\/resolve$/.exec(p); if (m && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await reports.resolve(m[1])); } if (p === '/api/admin/upload' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return handleUpload(req, res, 'admin'); } if (p === '/api/admin/rates' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { rates: ads.rates() }); } if (p === '/api/admin/drip' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { sequence: drip.sequence(), defaults: drip.DEFAULT_SEQUENCE, stats: await drip.stats(), mailReady: mailer.hasKey() }); } if (p === '/api/admin/drip' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = b.reset ? drip.resetSequence() : drip.setSequence(b.sequence); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/drip/test' && req.method === 'POST') { // send one step to the admin inbox if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); if (!ADMIN_EMAIL) return json(res, 400, { error: 'ADMIN_EMAIL is not set.' }); if (!mailer.hasKey()) return json(res, 400, { error: 'No mail key on the server.' }); try { const r = await drip.sendStep(ADMIN_EMAIL, Number(b.step) || 0, ADMIN_EMAIL); return json(res, r.error ? 400 : 200, r); } catch (e) { return json(res, 502, { error: 'Send failed: ' + e.message }); } } // wall fallback ads: shown in wall positions a member has not earned or filled, when no upline banner exists if (p === '/api/admin/wall-ads' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); let saved = null; try { saved = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'admin-wall-ads.json'), 'utf8')); } catch (e) {} return json(res, 200, { ads: Array.isArray(saved) ? saved : [], defaults: getAdminWallAds(), usingDefaults: !Array.isArray(saved) || !saved.length }); } if (p === '/api/admin/wall-ads' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const src = Array.isArray(b.ads) ? b.ads.slice(0, 20) : []; const out = []; for (const o of src) { const name = String((o && o.name) || '').trim().slice(0, 60); const targetUrl = String((o && o.targetUrl) || '').trim(); const bannerUrl = String((o && o.bannerUrl) || '').trim(); if (!targetUrl) continue; if (!/^https:\/\/[^\s]+$/i.test(targetUrl)) return json(res, 400, { error: 'Every wall ad needs an https:// link (' + (name || targetUrl) + ').' }); if (bannerUrl && !/^(\/uploads\/[a-z0-9]{24}\.(png|jpg|webp|gif)|https:\/\/[^\s]+)$/i.test(bannerUrl)) return json(res, 400, { error: 'Banner must be an uploaded image or an https image URL (' + (name || targetUrl) + ').' }); out.push({ name: name || 'InstantAdPay', targetUrl, bannerUrl: bannerUrl || null }); } const file = path.join(DATA_DIR, 'admin-wall-ads.json'); if (out.length) fs.writeFileSync(file, JSON.stringify(out, null, 2)); else { try { fs.unlinkSync(file); } catch (e) {} } return json(res, 200, { ok: true, ads: out, usingDefaults: !out.length }); } // -- growth snapshot: preview the post, or send it now if (p === '/api/admin/snapshot' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { text: await snapshot.preview() }); } if (p === '/api/admin/snapshot/send' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const r = await snapshot.post(); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/site' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { site: siteConfig() }); } // -- profit and loss from the chain index: volume, platform fees, member payouts, // pass-ups, per period (by block: ~43,200 Polygon blocks a day), plus the fee // 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; const evs = chain.recentEvents(1e9).filter(e => !days || e.block >= fromBlock); const sum = (list, f) => list.reduce((n, e) => n + BigInt(f(e) || '0'), 0n); const pol = wei => Number(BigInt(wei) / 10n ** 12n) / 1e6; // 6-decimal POL, no float drift const purchases = evs.filter(e => e.type === 'Purchase'); const tier = evs.filter(e => e.type === 'TierPaid'); const admin = evs.filter(e => e.type === 'AdminPaid'); const passed = evs.filter(e => e.type === 'PassedUp'); const byTier = {}; for (const t of [1, 2, 3]) byTier[t] = sum(tier.filter(e => e.tier === t), e => e.amountWei).toString(); const byPkg = {}; for (const e of purchases) { const k = '$' + Math.round(e.priceCents / 100); byPkg[k] = (byPkg[k] || 0) + 1; } let polUsd = 0; try { const cat = await chain.catalog(); const pk = (cat.products || cat).find(x => x.costWei); if (pk) polUsd = (pk.priceCents / 100) / (Number(BigInt(pk.costWei)) / 1e18); } catch (e) {} const wallets = { feeA: '0x7627fc78876948ac9d95c1c9eb061e7d6d647b70', feeB: '0x8b7d33849a2c4d92c985be31e46dc564ba901ad2', engine: burner.status().address || null }; const balances = {}; for (const [k, a] of Object.entries(wallets)) { if (!a) continue; try { balances[k] = BigInt(await chain.rpc('eth_getBalance', [a, 'latest'])).toString(); } catch (e) { balances[k] = null; } } return json(res, 200, { days, fromBlock, latest, polUsd, 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(), polhunter: hunt, // hand-entered movements in and out of the receivers: draws the chain cannot explain, and // outside income (a ClickBaitPays withdrawal, say) that is not an AdminPaid event ledger: { entries: ledger.entries(), totals: ledger.totals('feeA'), reconcile: ledger.reconcile(pol(sum(admin, e => e.amountWei)), pol(BigInt(balances.feeA || '0')), 'feeA') } }); } if (p === '/api/admin/ledger' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = ledger.put(b); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/ledger/delete' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = ledger.remove(String(b.id || '')); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/burner' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, burner.status()); } if (p === '/api/admin/burner/run' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, await burner.tick()); } // -- admin (Bearer ADMIN_PASSWORD, or the /admin portal session) if (p === '/api/admin/burns' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); return json(res, 200, { pending: await ads.pendingBurns() }); } if (p === '/api/admin/burns/mark' && req.method === 'POST') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const r = await ads.markBurned(b.id, b.tx); return json(res, r.error ? 400 : 200, r); } if (p === '/api/admin/rates' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); return json(res, 200, { ok: true, rates: ads.setRates(b) }); } if (p === '/api/admin/site' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const cur = siteConfig(); fs.writeFileSync(SITE_FILE, JSON.stringify(Object.assign(cur, b), null, 2)); return json(res, 200, { ok: true, site: siteConfig() }); } if (p === '/api/admin/chain' && req.method === 'PATCH') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const b = await readBody(req); const file = path.join(DATA_DIR, 'config.json'); let cur = {}; try { cur = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) {} fs.writeFileSync(file, JSON.stringify(Object.assign(cur, b), null, 2)); chain.reloadConfig(); return json(res, 200, { ok: true, config: chain.getConfig() }); } // -- pages (HEAD answered like GET so link previewers and crawlers see 200; Node drops the body) if (req.method === 'GET' || req.method === 'HEAD') { if (p === '/') { // arrived from PolHunter: remember it for 30 days so the dashboard can send them back once a wallet is linked if (u.searchParams.get('from') === 'polhunter') { res.writeHead(302, { Location: '/', 'Set-Cookie': 'iap.return=polhunter; Path=/; SameSite=Lax; Max-Age=' + (30 * 24 * 3600) + (IS_PROD ? '; Secure' : ''), 'Cache-Control': 'no-store' }); return res.end(); } return sendFile(res, path.join(PUBLIC_DIR, 'index.html')); } if (p === '/ledger') return sendFile(res, path.join(PUBLIC_DIR, 'ledger.html')); if (p === '/contract') return sendFile(res, path.join(PUBLIC_DIR, 'contract.html')); if (p === '/terms') return sendFile(res, path.join(PUBLIC_DIR, 'terms.html')); if (p === '/privacy') return sendFile(res, path.join(PUBLIC_DIR, 'privacy.html')); if (p === '/disclaimer') return sendFile(res, path.join(PUBLIC_DIR, 'disclaimer.html')); if (p === '/my') return sendFile(res, path.join(PUBLIC_DIR, 'my.html')); if (p === '/admin') return sendFile(res, path.join(PUBLIC_DIR, 'admin.html')); if (p === '/shorts') return sendFile(res, path.join(PUBLIC_DIR, 'shorts.html')); if (p === '/plays') return sendFile(res, path.join(PUBLIC_DIR, 'plays.html')); if (p === '/partners') return sendFile(res, path.join(PUBLIC_DIR, 'partners.html')); // site-owner kit (Marty, 2026-09-12) if (p === '/earning') return sendFile(res, path.join(PUBLIC_DIR, 'earning.html')); // member guide: how earning works (2026-09-12) // -- badge share page + image: /b// (OG preview = the member's composed badge) (Marty, 2026-09-13) m = /^\/badge-img\/([a-z0-9_]{3,20})\/(payouts|firstBuyer|level2|level3)\.jpg$/.exec(p); if (m) return sendFile(res, path.join(UPLOADS_DIR, 'badge-' + m[1] + '-' + m[2] + '.jpg')); m = /^\/b\/([a-z0-9_]{3,20})\/(payouts|firstBuyer|level2|level3)$/i.exec(p); if (m) { const un = m[1].toLowerCase(), key = m[2]; const a = await accounts.byUsername(un); const file = path.join(UPLOADS_DIR, 'badge-' + un + '-' + key + '.jpg'); if (!a || !fs.existsSync(file) || !(await ads.milestonesOf(a.email)).includes(key)) { res.writeHead(404, baseHeaders({ 'Content-Type': 'text/plain' })); return res.end('Not found'); } const [label, sub] = BADGE_META[key]; const esc = t => String(t || '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); const url = 'https://instantadpay.com/b/' + un + '/' + key, img = 'https://instantadpay.com/badge-img/' + un + '/' + key + '.jpg'; const title = '@' + un + ' unlocked ' + label + ' on InstantAdPay'; const desc = label + ': ' + sub + '. InstantAdPay pays sponsors in the same transaction, on-chain. Join @' + un + '\u2019s line free.'; const html = '' + esc(title) + '' + '' + '' + '' + '' + '' + '
' + esc(label) + ' badge for @' + esc(un) + '

@' + esc(un) + ' unlocked ' + label + '

' + esc(sub.charAt(0).toUpperCase() + sub.slice(1)) + '. On InstantAdPay every ad package that sells pays the sponsor in the same transaction, straight to their wallet, on-chain.

' + 'Join @' + esc(un) + '\u2019s line free

Advertising with a performance referral program. Not an investment; no income is guaranteed.

' + ''; res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=300' })); return res.end(html); } // -- blog: server-rendered so crawlers get real HTML + metadata (Marty, 2026-09-12) if (p === '/blog' || p === '/blog/' || /^\/blog\/page\/\d+$/.test(p) || /^\/blog\/tag\/[^/]+$/.test(p)) { const posts = await blog.listPublished(); const pg = (m = /^\/blog\/page\/(\d+)$/.exec(p)) ? Number(m[1]) : 1; const tag = (m = /^\/blog\/tag\/([^/]+)$/.exec(p)) ? decodeURIComponent(m[1]).toLowerCase() : null; res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=300' })); return res.end(blog.renderIndex(posts, pg, tag)); } m = /^\/handout\/([a-z0-9_]{3,20})$/i.exec(p); if (m) { const h = await toolkit.handout(m[1].toLowerCase()); if (!h) { res.writeHead(404, baseHeaders({ 'Content-Type': 'text/plain' })); return res.end('Not found'); } res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' })); return res.end(h); } if (p === '/leaderboard') { res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=60' })); return res.end(await leaderboard.renderPage()); } if (p === '/whats-new') { res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=120' })); return res.end(releases.renderPage()); } if (p === '/blog/feed.xml') { res.writeHead(200, baseHeaders({ 'Content-Type': 'application/rss+xml; charset=utf-8', 'Cache-Control': 'public, max-age=900' })); return res.end(blog.rss(await blog.listPublished())); } if (p === '/sitemap.xml') { res.writeHead(200, baseHeaders({ 'Content-Type': 'application/xml; charset=utf-8', 'Cache-Control': 'public, max-age=3600' })); return res.end(blog.sitemap(await blog.listPublished())); } if (p === '/robots.txt') { res.writeHead(200, baseHeaders({ 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'public, max-age=3600' })); return res.end(blog.robots()); } m = /^\/blog\/([a-z0-9-]{1,80})$/.exec(p); if (m) { const post = await blog.get(m[1]); const preview = !!(post && post.status !== 'published' && isAdmin(req)); // admins can open a draft at its real URL if (!post || (post.status !== 'published' && !preview)) { res.writeHead(404, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8' })); return res.end(blog.renderIndex(await blog.listPublished(), 1, null).replace('', '<title>Not found | ')); } if (!preview && req.method === 'GET') blog.bumpViews(post.slug); const related = blog.relatedFor(post, await blog.listPublished()); res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': preview ? 'no-store' : 'public, max-age=300' })); return res.end(blog.renderPost(post, related)); } if (p === '/wallets') return sendFile(res, path.join(PUBLIC_DIR, 'wallets.html')); if (p === '/launch') return sendFile(res, path.join(PUBLIC_DIR, 'launch.html')); if (/^\/view\/[a-f0-9]{32}$/.test(p)) return sendFile(res, path.join(PUBLIC_DIR, 'view.html')); m = /^\/uploads\/([a-z0-9]{24}\.(?:png|jpg|webp|gif|mp4|webm))$/.exec(p); if (m) return sendFile(res, path.join(UPLOADS_DIR, m[1])); if (/^\/tx\/0x[0-9a-fA-F]{64}$/.test(p)) return sendFile(res, path.join(PUBLIC_DIR, 'tx.html')); m = /^\/wall\/([A-Za-z0-9_]{1,20})$/.exec(p); if (m) { // server-inject per-member OG tags so shared bio links preview correctly (crawlers don't run JS) try { const tok = m[1].toLowerCase(); let a = await accounts.byUsername(tok); if (!a) a = await accounts.byCode(tok); let html = fs.readFileSync(path.join(PUBLIC_DIR, 'wall.html'), 'utf8'); if (a) { const nm = a.username ? '@' + a.username : 'member #' + (a.memberId || 0); const esc = t => String(t || '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); const title = nm + ' on InstantAdPay'; const desc = a.bio ? esc(a.bio).slice(0, 200) : 'Join ' + nm + 'โ€™s line on InstantAdPay โ€” instant on-chain ad payouts, free to join.'; const img = a.avatarUrl && /^https:/.test(a.avatarUrl) ? a.avatarUrl : 'https://instantadpay.com/banners/iap-hero-1200x630.png'; const url = 'https://instantadpay.com/wall/' + (a.username || a.code); const og = [ '<meta property="og:type" content="profile">', '<meta property="og:site_name" content="InstantAdPay">', '<meta property="og:url" content="' + url + '">', '<meta property="og:title" content="' + esc(title) + '">', '<meta property="og:description" content="' + desc + '">', '<meta property="og:image" content="' + esc(img) + '">', '<meta name="twitter:card" content="summary_large_image">', '<meta name="twitter:title" content="' + esc(title) + '">', '<meta name="twitter:description" content="' + desc + '">', '<meta name="twitter:image" content="' + esc(img) + '">', '<meta name="description" content="' + desc + '">', '<link rel="canonical" href="' + url + '">' ].join('\n'); html = html.replace('<title>Banner wall | InstantAdPay', '' + esc(title) + '').replace('', og); } res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' })); return res.end(html); } catch (e) { return sendFile(res, path.join(PUBLIC_DIR, 'wall.html')); } } const safe = path.normalize(p).replace(/^([.\\/])+/, ''); const file = path.join(PUBLIC_DIR, safe); if (file.startsWith(PUBLIC_DIR) && fs.existsSync(file) && fs.statSync(file).isFile()) return sendFile(res, file); } res.writeHead(404, baseHeaders({ 'Content-Type': 'text/plain' })); res.end('Not found'); } catch (e) { console.error('request error', req.url, e.message); try { json(res, 500, { error: 'server error' }); } catch (_) {} } }); boot().then(() => { server.listen(PORT, () => console.log(`InstantAdPay site on :${PORT} โ€” chain: ${chain.getConfig().chainName} โ€” store: ${db.enabled() ? 'MySQL' : 'volume JSON'}`)); }).catch(e => { console.error('boot failed:', e.message); process.exit(1); });