Wall ownership ladder: positions 2 and 3 become the member's own at 2 / 5 qualifying buyers

- accounts.wall_offers (JSON, up to 2 offers: label, https link, banner) set from
  Profile > Your wall; upload supported; locks show the buyer threshold.
- /api/wall assembles: position 1 = own line banner; positions 2-3 = own offer
  when unlocked and set, else upline banners (only uplines with a live banner),
  else house ads. Response carries unlocked + buyerCount; wall labels show
  "this wall" / "their line" / "InstantAdPay".
- Chatbot canned answer + facts, follow-up email 7 mention the ladder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 15:31:45 -05:00
parent 8b25361eef
commit 2ac6549171
21 changed files with 192 additions and 69 deletions
+13 -2
View File
@@ -34,7 +34,7 @@ function newCode(taken) {
} }
const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null, const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null,
username: a.username || null, memberId: a.memberId || 0, joinedVia: a.joinedVia || null, username: a.username || null, memberId: a.memberId || 0, joinedVia: a.joinedVia || null,
lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null, lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null, wallOffers: a.wallOffers || null,
avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials: a.socials || null, avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials: a.socials || null,
chatAvailable: a.chatAvailable === false ? false : true, lastSeen: a.lastSeen || 0, chatAvailable: a.chatAvailable === false ? false : true, lastSeen: a.lastSeen || 0,
address: a.address || null, created: a.created } : null; address: a.address || null, created: a.created } : null;
@@ -109,6 +109,12 @@ const J = {
this.save(); this.save();
return { ok: true, account: pub(acct) }; return { ok: true, account: pub(acct) };
}, },
async setWallOffers(e, json) {
const acct = this.db.byEmail[e];
if (!acct) return { error: 'No such account.' };
acct.wallOffers = json || null; this.save();
return { ok: true, account: pub(acct) };
},
async setProfile(e, avatarUrl, bio, socials) { async setProfile(e, avatarUrl, bio, socials) {
const acct = this.db.byEmail[e]; const acct = this.db.byEmail[e];
if (!acct) return { error: 'No such account.' }; if (!acct) return { error: 'No such account.' };
@@ -172,7 +178,7 @@ const J = {
// ---- MySQL mode ---- // ---- MySQL mode ----
const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code, const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code,
username: r.username, memberId: r.member_id || 0, joinedVia: r.joined_via || null, username: r.username, memberId: r.member_id || 0, joinedVia: r.joined_via || null,
lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url, lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url, wallOffers: r.wall_offers || null,
avatarUrl: r.avatar_url, bio: r.bio, socials: r.socials, avatarUrl: r.avatar_url, bio: r.bio, socials: r.socials,
chatAvailable: r.chat_available === 0 ? false : true, lastSeen: Number(r.last_seen || 0), chatAvailable: r.chat_available === 0 ? false : true, lastSeen: Number(r.last_seen || 0),
address: r.address, created: Number(r.created) }) : null; address: r.address, created: Number(r.created) }) : null;
@@ -224,6 +230,10 @@ const D = {
await db.q('UPDATE accounts SET line_banner_url=?, line_target_url=? WHERE email=?', [bannerUrl || null, targetUrl || null, e]); await db.q('UPDATE accounts SET line_banner_url=?, line_target_url=? WHERE email=?', [bannerUrl || null, targetUrl || null, e]);
return { ok: true, account: await this.byEmail(e) }; return { ok: true, account: await this.byEmail(e) };
}, },
async setWallOffers(e, json) {
await db.q('UPDATE accounts SET wall_offers=? WHERE email=?', [json || null, e]);
return { ok: true, account: await this.byEmail(e) };
},
async setProfile(e, avatarUrl, bio, socials) { async setProfile(e, avatarUrl, bio, socials) {
if (avatarUrl !== undefined) await db.q('UPDATE accounts SET avatar_url=? WHERE email=?', [avatarUrl || null, e]); if (avatarUrl !== undefined) await db.q('UPDATE accounts SET avatar_url=? WHERE email=?', [avatarUrl || null, e]);
if (bio !== undefined) await db.q('UPDATE accounts SET bio=? WHERE email=?', [bio || null, e]); if (bio !== undefined) await db.q('UPDATE accounts SET bio=? WHERE email=?', [bio || null, e]);
@@ -375,6 +385,7 @@ async function getChatSettings(email) {
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername, listAll, setSponsorRef, module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername, listAll, setSponsorRef,
setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count, setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count,
setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t), setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t),
setWallOffers: (e, j) => impl().setWallOffers(String(e || '').toLowerCase(), j),
setProfile: (e, a, bio, socials) => impl().setProfile(String(e || '').toLowerCase(), a, bio, socials), setProfile: (e, a, bio, socials) => impl().setProfile(String(e || '').toLowerCase(), a, bio, socials),
touchSeen: e => impl().touchSeen(String(e || '').toLowerCase()), touchSeen: e => impl().touchSeen(String(e || '').toLowerCase()),
setChatAvailable: (e, v) => impl().setChatAvailable(String(e || '').toLowerCase(), v), setChatAvailable: (e, v) => impl().setChatAvailable(String(e || '').toLowerCase(), v),
+3
View File
@@ -37,6 +37,8 @@ const CANNED = [
a: 'Promo tools (Members > Promo tools) has pill menus for each kit: Social posts (X, Facebook, LinkedIn, Telegram or WhatsApp, with post and share buttons), Text a friend (SMS-sized messages with Text it, WhatsApp, Telegram and Copy), Email swipes (short, standard, long, follow-up), Banners (every standard ad size plus square, story and Telegram sizes, download or copy URL), your Banner wall link, and an Objection handling bank with the truth plus a ready-to-send reply. Everything is personalized with your invite link.' }, a: 'Promo tools (Members > Promo tools) has pill menus for each kit: Social posts (X, Facebook, LinkedIn, Telegram or WhatsApp, with post and share buttons), Text a friend (SMS-sized messages with Text it, WhatsApp, Telegram and Copy), Email swipes (short, standard, long, follow-up), Banners (every standard ad size plus square, story and Telegram sizes, download or copy URL), your Banner wall link, and an Objection handling bank with the truth plus a ready-to-send reply. Everything is personalized with your invite link.' },
{ re: /(drain (my|your) wallet|stop and go back|trust wallet.*(warn|block|red)|wallet (warning|blocked))/i, { re: /(drain (my|your) wallet|stop and go back|trust wallet.*(warn|block|red)|wallet (warning|blocked))/i,
a: 'That red "this transaction will drain your wallet" screen is the wallet\'s own safety rule, not a problem with the purchase: Trust Wallet blocks any transaction that spends most of the POL in the wallet. Ways through: buy a smaller package first, add some POL so the purchase is well under half the balance, or connect a different wallet (MetaMask, Phantom, SafePal have no such block). Extra POL always stays yours. If the next try says WalletConnect disconnected, open Wallet, tap Disconnect, then Connect again. Phantom, SafePal and MetaMask do not have the hard block.' }, a: 'That red "this transaction will drain your wallet" screen is the wallet\'s own safety rule, not a problem with the purchase: Trust Wallet blocks any transaction that spends most of the POL in the wallet. Ways through: buy a smaller package first, add some POL so the purchase is well under half the balance, or connect a different wallet (MetaMask, Phantom, SafePal have no such block). Extra POL always stays yours. If the next try says WalletConnect disconnected, open Wallet, tap Disconnect, then Connect again. Phantom, SafePal and MetaMask do not have the hard block.' },
{ re: /(wall (page|slots?|positions?)|banner wall|my wall|three (ads|slots) on (my|the) wall)/i,
a: 'Your wall (instantadpay.com/wall/yourname) shows three ads. Position 1 is always your line banner. Positions 2 and 3 show your upline (or InstantAdPay) until you earn them: 2 qualifying buyers ($20 or more) make position 2 yours, 5 make position 3 yours, so a fully qualified member owns the whole page with their own links and offers. Set them in Profile > Your wall; an unlocked slot you leave empty keeps showing your upline until you fill it.' },
{ re: /(solo ad|inbox ad|inbox)/i, { re: /(solo ad|inbox ad|inbox)/i,
a: 'Solo ads are full-message ads delivered straight into member inboxes on-site. Compose one under Campaigns (pick "Solo ad"): subject line, a rich-text message with a real editor (bold, headings, lists, links), an attached image or video if you want one, and a call-to-action button with your own label. You pay 5 credits per guaranteed delivery, 10 deliveries minimum. On the reading side, your Inbox section collects solos from other members — give one a real read (10 seconds on the open message) and claim 2 credits, up to 5 rewarded reads a day. You never receive your own solo.' }, a: 'Solo ads are full-message ads delivered straight into member inboxes on-site. Compose one under Campaigns (pick "Solo ad"): subject line, a rich-text message with a real editor (bold, headings, lists, links), an attached image or video if you want one, and a call-to-action button with your own label. You pay 5 credits per guaranteed delivery, 10 deliveries minimum. On the reading side, your Inbox section collects solos from other members — give one a real read (10 seconds on the open message) and claim 2 credits, up to 5 rewarded reads a day. You never receive your own solo.' },
{ re: /(chat|message (my )?(sponsor|upline|team|downline)|talk to (my )?sponsor|contact (my )?sponsor|ask (my )?sponsor|reach (my )?sponsor)/i, { re: /(chat|message (my )?(sponsor|upline|team|downline)|talk to (my )?sponsor|contact (my )?sponsor|ask (my )?sponsor|reach (my )?sponsor)/i,
@@ -60,6 +62,7 @@ FACTS:
- PROMO TOOLS (Members > Promo tools, pill menu): Social posts for X/Facebook/LinkedIn/Telegram-WhatsApp with post/share buttons; Text a friend (5 SMS-sized messages with Text it / WhatsApp / Telegram / Copy); Email swipes (short, standard, long, follow-up); Banners in every standard ad size plus 1080x1080, 1080x1920 and 1280x720 (download or copy URL); the member's Banner wall link; an Objection handling bank (truth + ready-to-send reply); a Videos tab (hook videos in production). Every piece carries the member's invite link; angle links add ?v=instant|adspend|free|ledger. Members who want copy in their own voice can use mybrandedvoice.com. - PROMO TOOLS (Members > Promo tools, pill menu): Social posts for X/Facebook/LinkedIn/Telegram-WhatsApp with post/share buttons; Text a friend (5 SMS-sized messages with Text it / WhatsApp / Telegram / Copy); Email swipes (short, standard, long, follow-up); Banners in every standard ad size plus 1080x1080, 1080x1920 and 1280x720 (download or copy URL); the member's Banner wall link; an Objection handling bank (truth + ready-to-send reply); a Videos tab (hook videos in production). Every piece carries the member's invite link; angle links add ?v=instant|adspend|free|ledger. Members who want copy in their own voice can use mybrandedvoice.com.
- INVITE PAGES: a member's link instantadpay.com/join/<username> opens a lead-capture page (email first, wallet later); add ?v=instant|adspend|free|ledger|two for an angle-matched headline. New free members get a short getting-started email series over the first week (unsubscribe link in every email; the admin edits the sequence in /admin > Settings). - INVITE PAGES: a member's link instantadpay.com/join/<username> opens a lead-capture page (email first, wallet later); add ?v=instant|adspend|free|ledger|two for an angle-matched headline. New free members get a short getting-started email series over the first week (unsubscribe link in every email; the admin edits the sequence in /admin > Settings).
- WALLET DRAIN WARNING: Trust Wallet hard-blocks any purchase that spends most of the wallet's POL ("this transaction will drain your wallet", only "Stop and go back"). It is a balance-proportion heuristic, not a contract issue (there are no token approvals; a buy is one native-POL payable call). Advice: smaller package first, or add POL so the buy is well under half the balance, or use MetaMask/Phantom/SafePal (no hard block). The dashboard warns Trust Wallet users before sending when a buy would use more than ~55% of the balance; other wallets are not prompted. After a blocked attempt the WalletConnect session may be dead: Wallet tab > Disconnect > Connect again. - WALLET DRAIN WARNING: Trust Wallet hard-blocks any purchase that spends most of the wallet's POL ("this transaction will drain your wallet", only "Stop and go back"). It is a balance-proportion heuristic, not a contract issue (there are no token approvals; a buy is one native-POL payable call). Advice: smaller package first, or add POL so the buy is well under half the balance, or use MetaMask/Phantom/SafePal (no hard block). The dashboard warns Trust Wallet users before sending when a buy would use more than ~55% of the balance; other wallets are not prompted. After a blocked attempt the WalletConnect session may be dead: Wallet tab > Disconnect > Connect again.
- WALL OWNERSHIP LADDER: the public wall has 3 positions. Position 1 = the member's line banner. Positions 2 and 3 show upline banners (then house ads) UNTIL the member earns them: 2 qualifying buyers ($20+) unlock position 2, 5 unlock position 3; a fully qualified member's wall is 100% their own links/offers (set in Profile > Your wall: label, https link, optional banner image). Empty unlocked slots fall back to upline banners, then house ads.
- LINE BANNER (free, set in Profile): every member can set a destination URL (must allow framing) plus an optional banner image. It is shown to their next THREE levels of new members during welcome tours (position 1 for directs, 2, 3 below), and on their public BANNER WALL at /wall/<username> — a shareable page showing their line ladder with their join link. Free viral traffic that compounds as the team grows; no credits spent. - LINE BANNER (free, set in Profile): every member can set a destination URL (must allow framing) plus an optional banner image. It is shown to their next THREE levels of new members during welcome tours (position 1 for directs, 2, 3 below), and on their public BANNER WALL at /wall/<username> — a shareable page showing their line ladder with their join link. Free viral traffic that compounds as the team grows; no credits spent.
- Ad packages: Micro $5/500 credits, Activation $20/2,000, Builder $50/5,500, Growth $100/12,000, Leader $250/32,500. Dollar-priced, settled in POL (Polygon) at the live Chainlink rate. 1 credit = 1 cent of ad delivery. - Ad packages: Micro $5/500 credits, Activation $20/2,000, Builder $50/5,500, Growth $100/12,000, Leader $250/32,500. Dollar-priced, settled in POL (Polygon) at the live Chainlink rate. 1 credit = 1 cent of ad delivery.
- 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. - 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.
+1
View File
@@ -185,6 +185,7 @@ async function bootstrap() {
INDEX (stopped, next_at) INDEX (stopped, next_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await alterSafe('ALTER TABLE accounts ADD COLUMN joined_via VARCHAR(20) NULL'); // ?v= angle the lead came in on await alterSafe('ALTER TABLE accounts ADD COLUMN joined_via VARCHAR(20) NULL'); // ?v= angle the lead came in on
await alterSafe('ALTER TABLE accounts ADD COLUMN wall_offers VARCHAR(2000) NULL'); // JSON [{title,bannerUrl,targetUrl}] for wall positions 2-3 (unlock at 2 / 5 qualifying buyers)
} }
// one-time import: only when the tables are empty and JSON files exist // one-time import: only when the tables are empty and JSON files exist
+37 -37
View File
@@ -1,37 +1,37 @@
[ [
{ {
"hours": 24, "hours": 24,
"subject": "What you already own on InstantAdPay", "subject": "What you already own on InstantAdPay",
"body": "If you created an InstantAdPay account yesterday and then got busy, no worries. It happens.\n\nBut I want to circle back on something you may have missed. Right now, sitting in your free account, you already have three things most people never realize they own.\n\n1. Your username is your invite link.\n\nThat free account came with a link that is already set to go. Pick a username, share the link, and anyone who joins through it is in your line. It is yours from day one.\n\n2. Welcome credits that unlock in minutes.\n\nTake the short welcome tour inside your dashboard and the system adds credits to your account. You can use them {{paid:on top of the ad credits you already bought|to run a small test campaign and see how everything works}}.\n\n3. Promo tools with the posts already written.\n\nYour dashboard has a Promo tools section. Inside it: social posts, text messages, email swipes, even banners, all already carrying your invite link. You do not have to write a word.\n\n{{paid:You already jumped in on a package, which is great. The Promo tools make sharing your link the easy part. Everything is set up and ready to go.|This email is not asking you to buy anything. I just wanted to show you what is already in your account, waiting.}}\n\nOne thing to do today: sign in at {{site}}/my, pick your username if you have not yet, and copy your invite link. Just knowing it is there makes everything else easier.\n\nYour link right now: {{link}}\n\nMore tomorrow.\n\nMarty\n\n{{footer}}" "body": "If you created an InstantAdPay account yesterday and then got busy, no worries. It happens.\n\nBut I want to circle back on something you may have missed. Right now, sitting in your free account, you already have three things most people never realize they own.\n\n1. Your username is your invite link.\n\nThat free account came with a link that is already set to go. Pick a username, share the link, and anyone who joins through it is in your line. It is yours from day one.\n\n2. Welcome credits that unlock in minutes.\n\nTake the short welcome tour inside your dashboard and the system adds credits to your account. You can use them {{paid:on top of the ad credits you already bought|to run a small test campaign and see how everything works}}.\n\n3. Promo tools with the posts already written.\n\nYour dashboard has a Promo tools section. Inside it: social posts, text messages, email swipes, even banners, all already carrying your invite link. You do not have to write a word.\n\n{{paid:You already jumped in on a package, which is great. The Promo tools make sharing your link the easy part. Everything is set up and ready to go.|This email is not asking you to buy anything. I just wanted to show you what is already in your account, waiting.}}\n\nOne thing to do today: sign in at {{site}}/my, pick your username if you have not yet, and copy your invite link. Just knowing it is there makes everything else easier.\n\nYour link right now: {{link}}\n\nMore tomorrow.\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 48, "hours": 48,
"subject": "You can watch every payment live", "subject": "You can watch every payment live",
"body": "A couple of days ago you joined InstantAdPay. Free account. Nothing to lose.\n\nMaybe you have looked around. Maybe you have not.\n\nHere is what matters about this platform. The thing that is different from every other traffic system you have seen.\n\nThe money.\n\nWhen anyone on InstantAdPay buys an ad package, a verified smart contract on Polygon splits it in the same transaction. No holding. No waiting. Nobody touches the funds before they move.\n\n50 percent goes to their direct sponsor. 20 percent to level two. 10 percent to level three. 20 percent stays with the platform.\n\nEvery cent lands in real wallets within seconds.\n\n{{paid:You have already bought a package, so you have seen this firsthand.|You have not bought a package yet, and here is the thing. You can verify all of this without spending a dime.}}\n\nOpen {{site}}/ledger. Every transaction is on the public ledger. No wallet needed. No login. Just every ad package, every split, every payment, in the order it happened.\n\nScroll through it. Pick a transaction. Follow it.\n\nThis is proof you can check yourself. Not a screenshot. Not a promise in an email. A live blockchain you can inspect right now.\n\nThe money does not sit in a company wallet waiting to be released. It just moves. Instantly. Permanently. On-chain.\n\nSpend a few minutes on the ledger. Then you will understand why this model works differently.\n\nYour link: {{link}}\n\nMarty\n\n{{footer}}" "body": "A couple of days ago you joined InstantAdPay. Free account. Nothing to lose.\n\nMaybe you have looked around. Maybe you have not.\n\nHere is what matters about this platform. The thing that is different from every other traffic system you have seen.\n\nThe money.\n\nWhen anyone on InstantAdPay buys an ad package, a verified smart contract on Polygon splits it in the same transaction. No holding. No waiting. Nobody touches the funds before they move.\n\n50 percent goes to their direct sponsor. 20 percent to level two. 10 percent to level three. 20 percent stays with the platform.\n\nEvery cent lands in real wallets within seconds.\n\n{{paid:You have already bought a package, so you have seen this firsthand.|You have not bought a package yet, and here is the thing. You can verify all of this without spending a dime.}}\n\nOpen {{site}}/ledger. Every transaction is on the public ledger. No wallet needed. No login. Just every ad package, every split, every payment, in the order it happened.\n\nScroll through it. Pick a transaction. Follow it.\n\nThis is proof you can check yourself. Not a screenshot. Not a promise in an email. A live blockchain you can inspect right now.\n\nThe money does not sit in a company wallet waiting to be released. It just moves. Instantly. Permanently. On-chain.\n\nSpend a few minutes on the ledger. Then you will understand why this model works differently.\n\nYour link: {{link}}\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 96, "hours": 96,
"subject": "That pending feeling..", "subject": "That pending feeling..",
"body": "A few days ago you signed up for InstantAdPay.\n\nMaybe you have already bought an ad package. Maybe you have not. Either way, I want to talk about something most of us in this business have gotten way too used to.\n\nThe hold.\n\nYou make a sale. You wait. Your account says pending. Or under review. Or funds released on the 15th. You refresh the page. Nothing. You email support. They say 3 to 5 business days. Or 7.\n\nIt is so normal we do not even question it anymore.\n\nA platform holds your money for days or weeks. They say it is for security. Or processing. Or some other reason that sounds fine until you think about it.\n\nWho is holding it? And what are they doing with it while you wait?\n\nInstantAdPay works differently. It runs on a public blockchain called Polygon. When someone buys a package, a verified smart contract splits the payment in that same transaction. 50 percent goes to their sponsor. 20 to level two. 10 to level three. 20 to the platform.\n\nIt lands in real wallets in seconds.\n\nNothing is ever held. There is nothing to withdraw. Every payment is public on the live ledger.\n\n{{paid:You have already seen it happen. Your purchase paid your sponsor's line instantly. No pending. No waiting. No support ticket. It just showed up.|You can see this for yourself right now. The live ledger is public at {{site}}/ledger. Every transaction. Every split. Every payout. No login, no under review. Just a contract that does what it says, every time. And the $5 package is the cheapest way to watch it happen with your own purchase.}}\n\nYour dashboard is waiting at {{site}}/my.\n\nMarty\n\n{{footer}}" "body": "A few days ago you signed up for InstantAdPay.\n\nMaybe you have already bought an ad package. Maybe you have not. Either way, I want to talk about something most of us in this business have gotten way too used to.\n\nThe hold.\n\nYou make a sale. You wait. Your account says pending. Or under review. Or funds released on the 15th. You refresh the page. Nothing. You email support. They say 3 to 5 business days. Or 7.\n\nIt is so normal we do not even question it anymore.\n\nA platform holds your money for days or weeks. They say it is for security. Or processing. Or some other reason that sounds fine until you think about it.\n\nWho is holding it? And what are they doing with it while you wait?\n\nInstantAdPay works differently. It runs on a public blockchain called Polygon. When someone buys a package, a verified smart contract splits the payment in that same transaction. 50 percent goes to their sponsor. 20 to level two. 10 to level three. 20 to the platform.\n\nIt lands in real wallets in seconds.\n\nNothing is ever held. There is nothing to withdraw. Every payment is public on the live ledger.\n\n{{paid:You have already seen it happen. Your purchase paid your sponsor's line instantly. No pending. No waiting. No support ticket. It just showed up.|You can see this for yourself right now. The live ledger is public at {{site}}/ledger. Every transaction. Every split. Every payout. No login, no under review. Just a contract that does what it says, every time. And the $5 package is the cheapest way to watch it happen with your own purchase.}}\n\nYour dashboard is waiting at {{site}}/my.\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 144, "hours": 144,
"subject": "What your free account can actually do..", "subject": "What your free account can actually do..",
"body": "So you signed up for InstantAdPay about a week ago.\n\nFree account, no password, no fuss. Maybe you checked the dashboard, thought okay, cool, and moved on.\n\nLet me show you what is actually sitting in that account.\n\nInstantAdPay is an ad platform on the Polygon blockchain. In plain English: people buy ad credits, people view ads and earn credits, and the whole thing runs on a smart contract that pays out instantly.\n\nNow, the ads themselves.\n\nSeven formats: banners, text ads, full-screen login ads, solo ads delivered into member inboxes, video ads, featured links, and verified visits.\n\nEvery single view is dwell-timed on the server. That means a real person sat there long enough for it to count. Not bots. Not drive-bys.\n\nOne credit equals one cent of ad delivery. Simple math.\n\nAnd here is the part that makes it work: members earn credits by viewing ads. So when you run a campaign, your ad is shown to people who are actively watching. They have a reason to pay attention.\n\n{{paid:You already bought a package. Good. Your credits are sitting in your account. Go to the Campaigns tab and launch your first real campaign today. Pick a format and let it run. Watch what happens.|You have not bought a package yet, and you do not have to start with one. Sign in at {{site}}/my. Take the welcome tour for your welcome credits, earn more by viewing ads, and launch a small test campaign with those. See how it feels. See the numbers.}}\n\nEither way, the best way to understand this thing is to actually use it.\n\nYour link: {{link}}\n\nMarty\n\n{{footer}}" "body": "So you signed up for InstantAdPay about a week ago.\n\nFree account, no password, no fuss. Maybe you checked the dashboard, thought okay, cool, and moved on.\n\nLet me show you what is actually sitting in that account.\n\nInstantAdPay is an ad platform on the Polygon blockchain. In plain English: people buy ad credits, people view ads and earn credits, and the whole thing runs on a smart contract that pays out instantly.\n\nNow, the ads themselves.\n\nSeven formats: banners, text ads, full-screen login ads, solo ads delivered into member inboxes, video ads, featured links, and verified visits.\n\nEvery single view is dwell-timed on the server. That means a real person sat there long enough for it to count. Not bots. Not drive-bys.\n\nOne credit equals one cent of ad delivery. Simple math.\n\nAnd here is the part that makes it work: members earn credits by viewing ads. So when you run a campaign, your ad is shown to people who are actively watching. They have a reason to pay attention.\n\n{{paid:You already bought a package. Good. Your credits are sitting in your account. Go to the Campaigns tab and launch your first real campaign today. Pick a format and let it run. Watch what happens.|You have not bought a package yet, and you do not have to start with one. Sign in at {{site}}/my. Take the welcome tour for your welcome credits, earn more by viewing ads, and launch a small test campaign with those. See how it feels. See the numbers.}}\n\nEither way, the best way to understand this thing is to actually use it.\n\nYour link: {{link}}\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 192, "hours": 192,
"subject": "The one thing nobody tells you about ad platforms..", "subject": "The one thing nobody tells you about ad platforms..",
"body": "This might be the most important email I send you about InstantAdPay.\n\nBecause there is a detail in how this works that most people do not notice until it is too late.\n\nIt is not about the ad credits, the formats, or even the commission structure.\n\nIt is about timing.\n\nHere is how it works. Your invite link is already live. If someone clicks it, signs up, and buys their first ad package, any package from $5 to $250, you receive 50 percent of that payment. The smart contract pays you in the same transaction. No holding tank, no withdrawal minimum, no waiting.\n\nThat is the upside everyone talks about.\n\nHere is what nobody mentions.\n\nThe moment a person buys their first package through any link, they are locked to that sponsor permanently. Every future package they buy pays that sponsor's line.\n\nSo if someone you know (a friend, a subscriber, a group contact) joins InstantAdPay through someone else's link and buys their first package there, they are locked to that other person for good.\n\n{{paid:You already bought, so your own placement is settled. Your link is live and paying.|If you have not bought yet, your link is still live. It still pays you 50 percent from anyone who clicks it today and buys.}}\n\nBut it will not capture the people you know unless you send it to them.\n\nOne person this week. Just put {{link}} where they will see it. That is all it takes.\n\n{{sponsor}} got their link from somewhere too. Same deal. Now it is your turn to pass it forward.\n\nMarty\n\n{{footer}}" "body": "This might be the most important email I send you about InstantAdPay.\n\nBecause there is a detail in how this works that most people do not notice until it is too late.\n\nIt is not about the ad credits, the formats, or even the commission structure.\n\nIt is about timing.\n\nHere is how it works. Your invite link is already live. If someone clicks it, signs up, and buys their first ad package, any package from $5 to $250, you receive 50 percent of that payment. The smart contract pays you in the same transaction. No holding tank, no withdrawal minimum, no waiting.\n\nThat is the upside everyone talks about.\n\nHere is what nobody mentions.\n\nThe moment a person buys their first package through any link, they are locked to that sponsor permanently. Every future package they buy pays that sponsor's line.\n\nSo if someone you know (a friend, a subscriber, a group contact) joins InstantAdPay through someone else's link and buys their first package there, they are locked to that other person for good.\n\n{{paid:You already bought, so your own placement is settled. Your link is live and paying.|If you have not bought yet, your link is still live. It still pays you 50 percent from anyone who clicks it today and buys.}}\n\nBut it will not capture the people you know unless you send it to them.\n\nOne person this week. Just put {{link}} where they will see it. That is all it takes.\n\n{{sponsor}} got their link from somewhere too. Same deal. Now it is your turn to pass it forward.\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 264, "hours": 264,
"subject": "Your link is live (and already loaded)", "subject": "Your link is live (and already loaded)",
"body": "That link of yours? It pays the moment someone buys.\n\n{{paid:You already know the feeling. Someone buys, the smart contract runs, and 50 percent lands in your wallet. In seconds.|It works like this: someone clicks your link, buys any package, and 50 percent of it lands in your wallet in the same transaction. In seconds.}}\n\nNobody has to wait. No holding period. No withdraw button, because nothing was ever held.\n\nThe money splits in the same transaction the buyer makes. It lands in real wallets. And every payment is public on the live ledger.\n\nBut here is what most people do not use right away. The Promo tools tab in your dashboard.\n\nOpen it and you will find social posts, text messages, email swipes, and banners. All carrying your link. All ready to copy and paste.\n\nPlus an objection bank with honest answers for when someone asks how it works.\n\nTwo qualifying buyers ($20 or more) open level two for you: 20 percent of what their people buy. Five open level three.\n\n{{paid:You are already in motion. Now use the tools to keep going.|You have not bought a package yet. That is fine. The tools work either way. Your link is live whether you buy or not.}}\n\nOne text from Promo tools today. That is all it takes to get your first buyer moving.\n\n{{site}}/my\n\nMarty\n\n{{footer}}" "body": "That link of yours? It pays the moment someone buys.\n\n{{paid:You already know the feeling. Someone buys, the smart contract runs, and 50 percent lands in your wallet. In seconds.|It works like this: someone clicks your link, buys any package, and 50 percent of it lands in your wallet in the same transaction. In seconds.}}\n\nNobody has to wait. No holding period. No withdraw button, because nothing was ever held.\n\nThe money splits in the same transaction the buyer makes. It lands in real wallets. And every payment is public on the live ledger.\n\nBut here is what most people do not use right away. The Promo tools tab in your dashboard.\n\nOpen it and you will find social posts, text messages, email swipes, and banners. All carrying your link. All ready to copy and paste.\n\nPlus an objection bank with honest answers for when someone asks how it works.\n\nTwo qualifying buyers ($20 or more) open level two for you: 20 percent of what their people buy. Five open level three.\n\n{{paid:You are already in motion. Now use the tools to keep going.|You have not bought a package yet. That is fine. The tools work either way. Your link is live whether you buy or not.}}\n\nOne text from Promo tools today. That is all it takes to get your first buyer moving.\n\n{{site}}/my\n\nMarty\n\n{{footer}}"
}, },
{ {
"hours": 336, "hours": 336,
"subject": "Two weeks in. Here is where you are at.", "subject": "Two weeks in. Here is where you are at.",
"body": "Two weeks since you joined InstantAdPay. Thought it was time for a quick recap of where things stand.\n\nYou have a free account that is already working for you. Your invite link is live. Anyone who joins through it is in your line, and locks to you as their sponsor at their first purchase.\n\n{{paid:You grabbed a package and have credits to advertise with. Every ad you run reaches real people with dwell-timed views across banners, text ads, solo inbox ads, video ads and more. Your budget delivers exactly what you paid for.|You have not picked up an ad package yet, and that is fine. Your free account is ready when you are. Packages start at $5.}}\n\nHere is the referral side, exactly as the contract has it. When someone in your line buys a package, a smart contract splits their payment instantly, and 50 percent goes to you as their direct sponsor. No waiting, no withdrawal step.\n\nTwo qualifying buyers (people you referred who bought $20 or more) open level two: 20 percent of what the people they refer buy. Five qualifying buyers open level three: 10 percent from one level deeper.\n\nEvery payment lands in real wallets in seconds and is public on the ledger. Nothing held, nothing to withdraw.\n\nYour sponsor {{sponsor}} is right there in your dashboard. Open Messages any time.\n\nAnd the Promo tools are loaded: social posts, text messages, email swipes, banners, an objection bank, all with your invite link baked in.\n\nFrom here, the InstantAdPay newsletter takes over with updates and new features.\n\nYou know where your dashboard is: {{site}}/my\nYour link: {{link}}\n\nMarty\n\n{{footer}}" "body": "Two weeks since you joined InstantAdPay. Thought it was time for a quick recap of where things stand.\n\nYou have a free account that is already working for you. Your invite link is live. Anyone who joins through it is in your line, and locks to you as their sponsor at their first purchase.\n\n{{paid:You grabbed a package and have credits to advertise with. Every ad you run reaches real people with dwell-timed views across banners, text ads, solo inbox ads, video ads and more. Your budget delivers exactly what you paid for.|You have not picked up an ad package yet, and that is fine. Your free account is ready when you are. Packages start at $5.}}\n\nHere is the referral side, exactly as the contract has it. When someone in your line buys a package, a smart contract splits their payment instantly, and 50 percent goes to you as their direct sponsor. No waiting, no withdrawal step.\n\nTwo qualifying buyers (people you referred who bought $20 or more) open level two: 20 percent of what the people they refer buy. Five qualifying buyers open level three: 10 percent from one level deeper.\n\nEvery payment lands in real wallets in seconds and is public on the ledger. Nothing held, nothing to withdraw.\n\nOne more thing those buyers unlock: your public wall. Two qualifying buyers make the second ad position on your wall yours, five make the third. At that point the whole page runs your links and nobody else's.\n\nYour sponsor {{sponsor}} is right there in your dashboard. Open Messages any time.\n\nAnd the Promo tools are loaded: social posts, text messages, email swipes, banners, an objection bank, all with your invite link baked in.\n\nFrom here, the InstantAdPay newsletter takes over with updates and new features.\n\nYou know where your dashboard is: {{site}}/my\nYour link: {{link}}\n\nMarty\n\n{{footer}}"
} }
] ]
+1 -1
View File
@@ -6,7 +6,7 @@
<title>Admin | InstantAdPay</title> <title>Admin | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
<style> <style>
.adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px} .adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px}
.adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em} .adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em}
+38
View File
@@ -1532,8 +1532,46 @@
const a = await (await fetch('/api/me')).json(); const a = await (await fetch('/api/me')).json();
fillLineBanner(a); fillLineBanner(a);
fillProfileDetails(a); fillProfileDetails(a);
// buyerCount + wallUnlocked live on the dashboard payload (chain read), not on /api/me
let d = {}; try { d = await (await fetch('/api/my/dashboard')).json(); } catch (e) {}
fillWallOffers(Object.assign({}, a, { buyerCount: d.buyerCount || 0, wallUnlocked: d.wallUnlocked || 1 }));
} catch (e) {} } catch (e) {}
} }
// ── wall positions 2 & 3: the member's own offers, unlocked by qualifying buyers ──
function fillWallOffers(a) {
if (!a || !$('wallOffersCard')) return;
const unlocked = a.wallUnlocked || 1, bc = a.buyerCount || 0;
const offers = Array.isArray(a.wallOffers) ? a.wallOffers : [];
const NEED = [2, 5];
for (let i = 0; i < 2; i++) {
const o = offers[i] || {};
const open = unlocked >= i + 2;
$('woTitle' + i).value = o.title || ''; $('woTarget' + i).value = o.targetUrl || ''; $('woBanner' + i).value = o.bannerUrl || '';
$('woPrev' + i).hidden = !o.bannerUrl; $('woPrev' + i).innerHTML = o.bannerUrl ? '<img src="' + o.bannerUrl + '" alt="">' : '';
$('woLock' + i).textContent = open ? 'yours' : 'unlocks at ' + NEED[i] + ' qualifying buyers (' + bc + '/' + NEED[i] + ')';
$('woSlot' + i).classList.toggle('locked', !open);
}
$('woStatus').textContent = unlocked >= 3 ? 'Fully qualified: all three wall positions are yours.'
: unlocked === 2 ? 'Position 2 is yours. ' + (5 - bc) + ' more qualifying buyer' + (5 - bc === 1 ? '' : 's') + ' and position 3 is too.'
: (2 - bc) + ' more qualifying buyer' + (2 - bc === 1 ? '' : 's') + ' ($20 or more) opens position 2. You can set your links now; they go live the moment a slot unlocks.';
}
document.querySelectorAll('.wo-upload').forEach(b => b.addEventListener('click', () => { const f = document.querySelector('.wo-file[data-slot="' + b.dataset.slot + '"]'); if (f) f.click(); }));
document.querySelectorAll('.wo-file').forEach(inp => inp.addEventListener('change', async () => {
const i = inp.dataset.slot, f = inp.files[0]; if (!f) return;
$('woInfo' + i).textContent = 'Uploading…';
try {
const r = await (await fetch('/api/my/upload', { method: 'POST', headers: { 'Content-Type': f.type }, body: f })).json();
if (r.error) { $('woInfo' + i).textContent = r.error; }
else { $('woBanner' + i).value = r.url; $('woInfo' + i).textContent = 'Uploaded'; $('woPrev' + i).hidden = false; $('woPrev' + i).innerHTML = '<img src="' + r.url + '" alt="">'; }
} catch (e) { $('woInfo' + i).textContent = 'Upload failed. Try again.'; }
inp.value = '';
}));
if ($('woSaveBtn')) $('woSaveBtn').addEventListener('click', busy2($('woSaveBtn'), async () => {
const offers = [0, 1].map(i => ({ title: $('woTitle' + i).value, targetUrl: $('woTarget' + i).value, bannerUrl: $('woBanner' + i).value }));
const r = await api('/api/my/wall-offers', { offers });
IAP.status('Wall positions saved.', 'ok');
await loadLineBanner();
}));
const SOCIALS = ['facebook', 'twitter', 'youtube', 'instagram', 'tiktok', 'telegram', 'linkedin', 'website']; const SOCIALS = ['facebook', 'twitter', 'youtube', 'instagram', 'tiktok', 'telegram', 'linkedin', 'website'];
function fillProfileDetails(a) { function fillProfileDetails(a) {
if (!a) return; if (!a) return;
+5
View File
@@ -610,3 +610,8 @@ img{max-width:100%}
.lin-row .earn{font-family:var(--mono);font-size:12px;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums} .lin-row .earn{font-family:var(--mono);font-size:12px;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.lin-row .earn.on{color:var(--mint);font-weight:700} .lin-row .earn.on{color:var(--mint);font-weight:700}
.wo-slot{border:1px solid var(--line);border-radius:12px;padding:12px 14px}
.wo-slot.locked{opacity:.6}
.wo-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.wo-slot img{max-width:100%;border-radius:8px}
+1 -1
View File
@@ -64,7 +64,7 @@
const creative = m.bannerUrl const creative = m.bannerUrl
? '<img src="' + m.bannerUrl + '" alt="' + safe + ' banner">' ? '<img src="' + m.bannerUrl + '" alt="' + safe + ' banner">'
: '<b>' + safe + '</b><br><span class="muted small">' + (m.targetUrl ? 'visit their site' : 'banner slot open') + '</span>'; : '<b>' + safe + '</b><br><span class="muted small">' + (m.targetUrl ? 'visit their site' : 'banner slot open') + '</span>';
d.innerHTML = '<div class="wall-pos">Position ' + (i + 1) + (i === 0 ? ' · this wall' : '') + '</div>' d.innerHTML = '<div class="wall-pos">Position ' + (i + 1) + (m.own ? ' · this wall' : m.admin ? ' · InstantAdPay' : ' · their line') + '</div>'
+ '<div class="wc-creative">' + creative + '</div>' + '<div class="wc-creative">' + creative + '</div>'
+ '<div class="wc-action"></div>' + '<div class="wc-action"></div>'
+ '<div class="small muted" style="margin-top:8px">' + safe + '</div>'; + '<div class="small muted" style="margin-top:8px">' + safe + '</div>';
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Disclaimer | InstantAdPay</title> <title>Disclaimer | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -20,7 +20,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
+1 -1
View File
@@ -6,7 +6,7 @@
<title>You're invited | InstantAdPay</title> <title>You're invited | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
<style> <style>
.jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0} .jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0}
.jn-hero{padding:34px 0 10px;text-align:left} .jn-hero{padding:34px 0 10px;text-align:left}
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
+33 -2
View File
@@ -5,7 +5,7 @@
<title>Member area | InstantAdPay</title> <title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -648,6 +648,37 @@
<p><button class="btn" id="lbSaveBtn">Save line banner</button></p> <p><button class="btn" id="lbSaveBtn">Save line banner</button></p>
<p class="muted small" id="lbCurrent"></p> <p class="muted small" id="lbCurrent"></p>
</div> </div>
<div class="card" id="wallOffersCard">
<h3>Your wall: positions 2 and 3</h3>
<p class="muted small">Your public wall shows three ads. Position 1 is your line banner above. Positions 2 and 3
start out showing your upline (or InstantAdPay). <b>Two qualifying buyers</b> make position 2 yours,
<b>five</b> make position 3 yours: your own links, your own offers, nobody else's ad on your page.
An unlocked slot you leave empty keeps showing your upline until you fill it.</p>
<p class="small" id="woStatus" style="color:var(--mint)"></p>
<div class="grid c2">
<div class="wo-slot" id="woSlot0">
<div class="wo-head"><b>Position 2</b> <span class="chip flat" id="woLock0"></span></div>
<p><input id="woTitle0" maxlength="60" placeholder="Label shown under the ad (optional)" style="width:100%"></p>
<p><input id="woTarget0" placeholder="Link (https://…)" style="width:100%"></p>
<p><input id="woBanner0" placeholder="Banner image URL (optional, or upload)" style="width:100%"></p>
<p><input type="file" class="wo-file" data-slot="0" accept="image/png,image/jpeg,image/webp,image/gif" hidden>
<button class="btn small sec wo-upload" data-slot="0" type="button">Upload image</button>
<span class="small muted" id="woInfo0"></span></p>
<div id="woPrev0" hidden style="margin:6px 0"></div>
</div>
<div class="wo-slot" id="woSlot1">
<div class="wo-head"><b>Position 3</b> <span class="chip flat" id="woLock1"></span></div>
<p><input id="woTitle1" maxlength="60" placeholder="Label shown under the ad (optional)" style="width:100%"></p>
<p><input id="woTarget1" placeholder="Link (https://…)" style="width:100%"></p>
<p><input id="woBanner1" placeholder="Banner image URL (optional, or upload)" style="width:100%"></p>
<p><input type="file" class="wo-file" data-slot="1" accept="image/png,image/jpeg,image/webp,image/gif" hidden>
<button class="btn small sec wo-upload" data-slot="1" type="button">Upload image</button>
<span class="small muted" id="woInfo1"></span></p>
<div id="woPrev1" hidden style="margin:6px 0"></div>
</div>
</div>
<p><button class="btn" id="woSaveBtn">Save wall positions</button></p>
</div>
<div class="card"> <div class="card">
<h3>Sponsor chat</h3> <h3>Sponsor chat</h3>
<p class="muted small">Your line can message you one-to-one for help, and you can message anyone <p class="muted small">Your line can message you one-to-one for help, and you can message anyone
@@ -739,7 +770,7 @@
<script src="/assets/common.js?v=20260909a"></script> <script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wallet.js?v=20260909c"></script> <script src="/assets/wallet.js?v=20260909c"></script>
<script src="/assets/promo.js?v=20260909b"></script> <script src="/assets/promo.js?v=20260909b"></script>
<script src="/assets/my.js?v=20260909h"></script> <script src="/assets/my.js?v=20260909i"></script>
<script src="/assets/chat.js?v=20260907l"></script> <script src="/assets/chat.js?v=20260907l"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Privacy Policy | InstantAdPay</title> <title>Privacy Policy | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Shorts | InstantAdPay</title> <title>Shorts | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
<style> <style>
html,body{height:100%;margin:0;overflow:hidden;background:#000} html,body{height:100%;margin:0;overflow:hidden;background:#000}
.sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)} .sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)}
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Terms of Service | InstantAdPay</title> <title>Terms of Service | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Transaction | InstantAdPay</title> <title>Transaction | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Viewing ad — InstantAdPay</title> <title>Viewing ad — InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
<style> <style>
html,body{height:100%;margin:0;overflow:hidden} html,body{height:100%;margin:0;overflow:hidden}
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)} .vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
+2 -2
View File
@@ -6,7 +6,7 @@
<!--OG--> <!--OG-->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909c"> <link rel="stylesheet" href="/assets/site.css?v=20260909d">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
@@ -40,6 +40,6 @@
</div> </div>
</section> </section>
<script src="/assets/common.js?v=20260909a"></script> <script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wall.js?v=20260909a"></script> <script src="/assets/wall.js?v=20260909b"></script>
</body> </body>
</html> </html>
+48 -14
View File
@@ -82,6 +82,14 @@ async function uplineSlides(email, depth = 3) {
} }
return out; 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 wallUnlockedFor = bc => (bc >= 5 ? 3 : bc >= 2 ? 2 : 1);
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. // 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}]). // Configurable by dropping data/admin-wall-ads.json ([{name,targetUrl,bannerUrl}]).
function getAdminWallAds() { function getAdminWallAds() {
@@ -681,6 +689,7 @@ const server = http.createServer(async (req, res) => {
// profile + line-banner fields so the Profile pane repopulates on reload (were being saved but not returned) // 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, avatarUrl: (acct && acct.avatarUrl) || null, bio: (acct && acct.bio) || null,
socials: (acct && acct.socials) || null, socials: (acct && acct.socials) || null,
wallOffers: parseWallOffers(acct),
lineBannerUrl: (acct && acct.lineBannerUrl) || null, lineTargetUrl: (acct && acct.lineTargetUrl) || null }; lineBannerUrl: (acct && acct.lineBannerUrl) || null, lineTargetUrl: (acct && acct.lineTargetUrl) || null };
if (memberId) { if (memberId) {
try { try {
@@ -772,6 +781,7 @@ const server = http.createServer(async (req, res) => {
status: r.address ? 'wallet linked' : 'joined free' status: r.address ? 'wallet linked' : 'joined free'
})); }));
out.isAdmin = !!(ADMIN_EMAIL && out.email && String(out.email).toLowerCase() === ADMIN_EMAIL); // shows the Admin link 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); return json(res, 200, out);
} }
if (p === '/api/my/profile' && req.method === 'POST') { if (p === '/api/my/profile' && req.method === 'POST') {
@@ -1129,6 +1139,25 @@ const server = http.createServer(async (req, res) => {
const r = await accounts.setProfile(s.email, avatar, bio, socials); const r = await accounts.setProfile(s.email, avatar, bio, socials);
return json(res, r.error ? 400 : 200, r); 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 // -- line banner: the member's viral slot on welcome tours + their wall
if (p === '/api/my/linebanner' && req.method === 'POST') { if (p === '/api/my/linebanner' && req.method === 'POST') {
const s = await auth.fromRequest(req); const s = await auth.fromRequest(req);
@@ -1175,31 +1204,36 @@ const server = http.createServer(async (req, res) => {
let a = await accounts.byUsername(tok); let a = await accounts.byUsername(tok);
if (!a) a = await accounts.byCode(tok); if (!a) a = await accounts.byCode(tok);
if (!a) return json(res, 404, { error: 'No wall under that name.' }); if (!a) return json(res, 404, { error: 'No wall under that name.' });
const ladder = [a, ...await uplineSlides(a.email, 2)] 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', .map(x => ({ name: x.username ? '@' + x.username : x.memberId ? 'member #' + x.memberId : 'a member',
bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl || null })); bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl, upline: true }));
// fill positions 2 & 3 with admin ads when there isn't enough upline, so the wall is never sparse const adAds = getAdminWallAds(); let ai = 0;
const adAds = getAdminWallAds(); 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 }; };
let ai = 0; const ladder = [{ name: ownName, bannerUrl: a.lineBannerUrl || null, targetUrl: a.lineTargetUrl || null, own: true }];
while (ladder.length < 3 && adAds.length) { for (let i = 1; i < 3; i++) {
const ad = adAds[ai++ % adAds.length]; const o = offers[i - 1];
ladder.push({ name: ad.name || 'InstantAdPay', bannerUrl: ad.bannerUrl || null, targetUrl: ad.targetUrl || 'https://instantadpay.com/', admin: true }); 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); const finalLadder = ladder.slice(0, 3);
// the wall owner's achievement badge (their highest reached tier) // the wall owner's achievement badge (their highest reached tier)
let badge = null; let badge = null;
if (a.memberId) { if (a.memberId) {
try { const t = bc >= 5 ? ['nexus', 'Nexus'] : bc >= 2 ? ['circuit', 'Circuit'] : bc >= 1 ? ['surge', 'Surge'] : ['spark', 'Spark'];
const bc = (await chain.member(a.memberId)).buyerCount || 0; badge = { img: '/badges/badge-' + t[0] + '.jpg?v=2', label: t[1] };
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] };
} catch (e) {}
} }
const joinPath = '/join/' + (a.username || a.code); const joinPath = '/join/' + (a.username || a.code);
let socials = null; try { socials = a.socials ? JSON.parse(a.socials) : null; } catch (e) {} 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), return json(res, 200, { name: a.username ? '@' + a.username : 'member #' + (a.memberId || 0),
avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials, badge, avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials, badge,
joinUrl: joinPath, qrUrl: '/api/qr?d=' + encodeURIComponent('https://instantadpay.com' + joinPath), ladder: finalLadder }); 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 // -- watch-to-earn video ads: serve one, then reward a server-clock-verified watch
if (p === '/api/my/videos' && req.method === 'GET') { if (p === '/api/my/videos' && req.method === 'GET') {