From 0c58b80184ea12ca909f5475287784f5d27c99ef Mon Sep 17 00:00:00 2001 From: martbost Date: Sun, 13 Sep 2026 13:50:28 -0500 Subject: [PATCH] Holding tank: no releasing an adoptee within 3 days of pickup, dropped adoptions count toward the two-adoption limit, releases posted to the feed and Telegram Co-Authored-By: Claude Fable 5.1 --- chatbot.js | 2 +- public/assets/my.js | 1 + public/my.html | 2 +- server.js | 9 +++++++++ tank.js | 16 ++++++++++++---- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/chatbot.js b/chatbot.js index ac6fdac..0e1ac5f 100644 --- a/chatbot.js +++ b/chatbot.js @@ -78,7 +78,7 @@ FACTS: - Live formats: display banners (per impression), text ads (per impression), full-screen LOGIN ADS (per day: right after a member signs in they land on a sponsor interstitial โ€” they click "Open Ad", the advertiser's page opens in a NEW tab, a countdown runs on the interstitial, and at zero a "Go to dashboard" button appears. Just a CTA link is enough; an optional banner image can be the clickable creative. No framing requirement since it opens in its own tab), WATCH-TO-EARN VIDEO ADS (advertiser uploads an MP4/WebM or gives a direct https .mp4/.webm link and picks a required watch length โ€” 10s/30s/60s โ€” which sets the per-view price; viewers watch in an escape-proof player under Earn credits > Watch videos, the watch time is enforced on the server clock, and they earn credits per completed watch; you never see your own videos), and solo ads. Banner ads also require a size (standard IAB sizes like 728x90, 300x250). Coming: featured rotation with disclosed rotation size, verified-visit packs. - WALLETS + BUYING POL (Training > Wallets and buying POL, /wallets, members only): preferred MetaMask (recommended; extra accounts for Qualified Start), Phantom (PHANTOM GOTCHA: Polygon is OFF by default; Settings > Active Networks > turn on Polygon, otherwise connect/buy fails or shows the wrong network; seen with Jim Watts' EB team 2026-09-13), SafePal, Coinbase Wallet; Trust works but blocks buys spending most of its POL (keep ~2x). MoonPay flow: connect wallet, Buy packages > "Buy POL with a card" opens MoonPay with POL on Polygon + the member's address prefilled; card/Apple Pay/Google Pay; first-time ID check; minimum order ~$30; buy package cost + 2-3 POL for fees; POL arrives in minutes; then buy. Exchanges: withdraw POL on the Polygon network. Never MATIC on Ethereum, never share the recovery phrase. - INTRO VIDEO ON THE WALL: Profile > social links has an "Intro video" field (YouTube, Vimeo or direct .mp4 link). It embeds on the member's public wall page (/wall/) right under their bio, above the three-level line and the join button. -- HOLDING TANK (Members > My line > Holding tank card): free members who joined with no sponsor wait there; a member who has switched on payouts AND bought their own $20+ package can Adopt one (first come, max 2 open adoptions, 7-day window; if the person never links a wallet or buys, they fall back into the tank; a person can be adopted twice at most). Adopting sets the sponsor, opens a chat and emails the member; their first purchase then binds to the adopter on-chain. Members can also "Release to tank" one of their own free referrals (pay it forward). Admin sees the tank under Members. +- HOLDING TANK (Members > My line > Holding tank card): free members who joined with no sponsor wait there; a member who has switched on payouts AND bought their own $20+ package can Adopt one (first come, max 2 open adoptions, 7-day window; if the person never links a wallet or buys, they fall back into the tank; a person can be adopted twice at most). Adopting sets the sponsor, opens a chat and emails the member; their first purchase then binds to the adopter on-chain. Members can also "Release to tank" one of their own free referrals (pay it forward), but NOT someone they adopted less than 3 days ago: an adoption is a commitment, and a dropped adoption still counts toward that person's two-adoption lifetime limit. Releases are posted to the feed and Telegram like pickups. Admin sees the tank under Members. - DAILY CLAIM STREAK: finishing the daily ad set and claiming pays 5 credits on day 1, 7 on day 2, 10 from day 3, and 25 on every 7th consecutive day; miss a day and it restarts. After the set, verified visits (up to 20 a day, 1 credit each) keep earning, and the credits are meant to be spent on a campaign. - BLOG (public, instantadpay.com/blog): Marty's coaching and teaching articles on building a line, advertising that pays, and daily habits; each article has its own page and can be shared; RSS at /blog/feed.xml. Members who want to write their own articles: not offered today. - HOLDING TANK ALERTS: when new members land in the tank, a note at the top of every member's Overview names them (usernames) and a post goes to the team's Telegram payments topic; adopt from My line > Holding tank (your own $20 package required). diff --git a/public/assets/my.js b/public/assets/my.js index 5880e4b..23757e2 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -296,6 +296,7 @@ // site-wide activity (not about me): joins, tank, adoptions, purchases, payouts, qualifications if (ev.type === 'Joined') { communityToast('๐Ÿ‘‹ ' + ev.name + ' just joined' + (ev.tank ? ' and is waiting for a sponsor in the holding tank' : '')); liveRefresh(); return; } if (ev.type === 'Adopted') { communityToast('๐Ÿค ' + ev.sponsor + ' picked up ' + ev.member + ' from the holding tank'); liveRefresh(); return; } + if (ev.type === 'Released') { communityToast('๐Ÿชฃ ' + ev.sponsor + ' returned ' + ev.member + ' to the holding tank'); liveRefresh(); return; } const mine = MYID && (ev.recipientId === MYID || ev.toId === MYID || ev.sponsorId === MYID || ev.skippedId === MYID || ev.buyerId === MYID); if (!mine) { if (ev.type === 'Purchase' && ev.buyerId) communityToast('๐Ÿงพ ' + nm(ev.buyerId) + ' just bought a $' + Math.round((ev.priceCents || 0) / 100) + ' package'); diff --git a/public/my.html b/public/my.html index f03a5ee..31508eb 100644 --- a/public/my.html +++ b/public/my.html @@ -915,7 +915,7 @@ - + diff --git a/server.js b/server.js index f81e253..d19b022 100644 --- a/server.js +++ b/server.js @@ -1279,6 +1279,15 @@ const server = http.createServer(async (req, res) => { 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 diff --git a/tank.js b/tank.js index f5a4043..c68acee 100644 --- a/tank.js +++ b/tank.js @@ -13,7 +13,8 @@ const db = require('./db'); const CAP_OPEN = 2; // open adoptions per adopter const TTL_MS = 7 * 86400000; // an adoption's window to convert -const MAX_ADOPTIONS = 2; // per adoptee, lifetime +const MAX_ADOPTIONS = 2; // per adoptee, lifetime (dropped adoptions count too, since 2026-09-13) +const HOLD_MS = 3 * 24 * 3600 * 1000; // an adoption is a commitment: no releasing someone you picked up less than 3 days ago (Marty, 2026-09-13) const MIN_OWN_BUY_CENTS = 2000; // adopter must have bought a $20+ package themselves const WARN_DAYS = 10; // dormant lead: sponsor warned after this many days without contact const RESCUE_DAYS = 14; // ...and the lead moves to the tank after this many (warning at least 4 days old) @@ -131,13 +132,20 @@ async function release(ownerEmail, directEmail) { const toks = [owner.code, owner.username, owner.memberId ? String(owner.memberId) : null].filter(Boolean).map(String); if (!toks.includes(String(direct.sponsorRef || ''))) return { error: 'That member is not in your line.' }; if (direct.memberId) return { error: 'That member has already bought; on-chain sponsorship cannot move.' }; + // picked up from the tank recently? then it is not theirs to drop yet (Bradley churned three in 80 seconds, 2026-09-13) + const openAds = (await impl().open(null)).filter(ad => ad.adoptee === d); + const young = openAds.find(ad => Date.now() - ad.ts < HOLD_MS); + if (young) { + const hrs = Math.ceil((HOLD_MS - (Date.now() - young.ts)) / 3600000); + return { error: 'You picked ' + nameOf(direct) + ' up from the tank ' + Math.max(1, Math.round((Date.now() - young.ts) / 3600000)) + ' hour(s) ago. An adoption is a commitment: message them, help them link a wallet, and if it goes nowhere you can release them in ' + hrs + ' hour(s).' }; + } const r = await accounts.setSponsorRef(d, ''); if (r.error) return r; - // an open adoption of this person closes as released (it no longer counts toward their two) + // an open adoption of this person closes as dropped, and a dropped adoption still counts toward their two let closed = 0; - for (const ad of await impl().open(null)) if (ad.adoptee === d) { await impl().setStatus(ad.id, 'released'); closed++; } + for (const ad of openAds) { await impl().setStatus(ad.id, 'dropped'); closed++; } if (!closed) await impl().add({ adoptee: d, adopter: o, ts: Date.now(), expires: Date.now(), status: 'released', note: 'released to the tank' }); - return { ok: true }; + return { ok: true, adopted: closed > 0, ownerName: nameOf(owner), memberName: nameOf(direct) }; } // open adoptions past their window: converted ones close as done; the rest fall