No-payout positions (siteConfig.noPayoutIds, default 25): linkage only. Buy-from picker disables them, purchases from them refused, joins routed away from them and their upline chain

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-14 10:30:58 -05:00
parent e699513636
commit 1fad5bdab2
5 changed files with 24 additions and 4 deletions
+16
View File
@@ -411,6 +411,7 @@ function siteConfig() {
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
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/<brand>
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)
@@ -494,6 +495,17 @@ async function attachNames(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;
}
async function resolveSponsorToken(tok) {
const t = String(tok || '').trim().toLowerCase();
if (!t) return 0;
@@ -828,6 +840,7 @@ const server = http.createServer(async (req, res) => {
const acct = s && s.email ? await accounts.byEmail(s.email) : null;
const tok = (acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor'] || '';
let sponsorId = await resolveSponsorToken(tok);
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;
@@ -1012,6 +1025,7 @@ const server = http.createServer(async (req, res) => {
const memberId = await auth.refreshMemberId(s);
const acct = (s.email && await accounts.byEmail(s.email)) || (s.address && await accounts.byAddress(s.address)) || null;
let sponsorId = await resolveSponsorToken((acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor']);
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(() => {});
@@ -1142,6 +1156,7 @@ const server = http.createServer(async (req, res) => {
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);
}
@@ -1149,6 +1164,7 @@ const server = http.createServer(async (req, res) => {
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; } };