Welcome credits (spec 8b earned pool v1) + official rehearsal rate card
Every account gets welcomeCredits (25, rate-card configurable) via an idempotent lazy grant on dashboard load, so existing members receive theirs retroactively. earned_credits table with JSON fallback; Overview stat shows the purchased/welcome split. Chatbot facts and canned answers synced; homepage free-membership list mentions the welcome batch. Rate card promoted from placeholder to decided and persisted in the volume. Spending integration for the earned pool is the next engine build. Assets v=20260905b. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,8 @@ function rates() {
|
|||||||
bannerBatch: 10, bannerCreditsPerBatch: 2,
|
bannerBatch: 10, bannerCreditsPerBatch: 2,
|
||||||
textBatch: 10, textCreditsPerBatch: 1,
|
textBatch: 10, textCreditsPerBatch: 1,
|
||||||
loginCreditsPerDay: 100,
|
loginCreditsPerDay: 100,
|
||||||
burnBatchMin: 50
|
burnBatchMin: 50,
|
||||||
|
welcomeCredits: 25 // PLACEHOLDER until Marty sets the rate card (spec §8b earned pool)
|
||||||
}, saved);
|
}, saved);
|
||||||
}
|
}
|
||||||
function setRates(patch) {
|
function setRates(patch) {
|
||||||
@@ -240,6 +241,43 @@ const D = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- earned/welcome credits (spec §8b second pool: engine-side, never
|
||||||
|
// on-chain; welcome grant is idempotent and lazy so existing accounts get
|
||||||
|
// theirs on next dashboard load) ----
|
||||||
|
const EJ = {
|
||||||
|
db: null,
|
||||||
|
FILE: () => path.join(DATA_DIR, 'earned.json'),
|
||||||
|
load() { try { this.db = JSON.parse(fs.readFileSync(this.FILE(), 'utf8')); } catch (e) { this.db = {}; } },
|
||||||
|
save() { try { fs.writeFileSync(this.FILE(), JSON.stringify(this.db)); } catch (e) {} }
|
||||||
|
};
|
||||||
|
async function earnedBalance(email) {
|
||||||
|
const e = String(email || '').toLowerCase();
|
||||||
|
if (!e) return 0;
|
||||||
|
if (db.enabled()) {
|
||||||
|
const r = await db.q('SELECT balance FROM earned_credits WHERE email=?', [e]);
|
||||||
|
return r.length ? r[0].balance : 0;
|
||||||
|
}
|
||||||
|
if (!EJ.db) EJ.load();
|
||||||
|
return (EJ.db[e] && EJ.db[e].balance) || 0;
|
||||||
|
}
|
||||||
|
async function grantWelcome(email) {
|
||||||
|
const e = String(email || '').toLowerCase();
|
||||||
|
if (!e) return 0;
|
||||||
|
const amount = rates().welcomeCredits || 0;
|
||||||
|
if (db.enabled()) {
|
||||||
|
await db.q(`INSERT INTO earned_credits (email,balance,granted_welcome,updated) VALUES (?,?,1,?)
|
||||||
|
ON DUPLICATE KEY UPDATE balance = balance + IF(granted_welcome=0, ?, 0),
|
||||||
|
granted_welcome = 1, updated = VALUES(updated)`, [e, amount, Date.now(), amount]);
|
||||||
|
return earnedBalance(e);
|
||||||
|
}
|
||||||
|
if (!EJ.db) EJ.load();
|
||||||
|
if (!EJ.db[e] || !EJ.db[e].welcomed) {
|
||||||
|
EJ.db[e] = { balance: ((EJ.db[e] && EJ.db[e].balance) || 0) + amount, welcomed: true };
|
||||||
|
EJ.save();
|
||||||
|
}
|
||||||
|
return (EJ.db[e] && EJ.db[e].balance) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
const impl = () => db.enabled() ? D : J;
|
const impl = () => db.enabled() ? D : J;
|
||||||
function init(opts) { DATA_DIR = opts.dataDir; chain = opts.chain; J.load(); }
|
function init(opts) { DATA_DIR = opts.dataDir; chain = opts.chain; J.load(); }
|
||||||
|
|
||||||
@@ -266,4 +304,4 @@ async function pendingBurns() { return impl().pendingBurns(); }
|
|||||||
async function markBurned(id, tx) { return impl().markBurned(id, tx); }
|
async function markBurned(id, tx) { return impl().markBurned(id, tx); }
|
||||||
|
|
||||||
module.exports = { init, rates, setRates, createCampaign, listCampaigns, setStatus,
|
module.exports = { init, rates, setRates, createCampaign, listCampaigns, setStatus,
|
||||||
serve, click, dailySweep, availableCredits, pendingBurns, markBurned };
|
serve, click, dailySweep, availableCredits, earnedBalance, grantWelcome, pendingBurns, markBurned };
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ const CANNED = [
|
|||||||
{ re: /(referral link|invite link|share link|refer)/i,
|
{ re: /(referral link|invite link|share link|refer)/i,
|
||||||
a: 'You get your share link the moment you sign in, free members included. One tip: switch on payouts (one free wallet step in Members) before your people start buying, because the contract locks each buyer to their sponsor at their first purchase.' },
|
a: 'You get your share link the moment you sign in, free members included. One tip: switch on payouts (one free wallet step in Members) before your people start buying, because the contract locks each buyer to their sponsor at their first purchase.' },
|
||||||
{ re: /(credit|impression|cpm|what do i get|what am i buying)/i,
|
{ re: /(credit|impression|cpm|what do i get|what am i buying)/i,
|
||||||
a: 'Packages mint ad credits on-chain. One credit is one cent of ad delivery: banners, text ads, and login ads across the network, managed from the campaign manager in Members. Only your campaigns can spend your credits.' },
|
a: 'Packages mint ad credits on-chain, and every new member also gets a small batch of welcome credits just for joining. One credit is one cent of ad delivery: banners, text ads, and login ads across the network, managed from the campaign manager in Members. Only your campaigns can spend your credits.' },
|
||||||
{ re: /(price|cost|package|how much is)/i,
|
{ re: /(price|cost|package|how much is)/i,
|
||||||
a: 'Packages run 5 to 250 dollars, priced in dollars and settled in POL at the live rate when you buy. The full ladder with live pricing is on the home page. Packages of $20 or more count toward qualification.' },
|
a: 'Packages run 5 to 250 dollars, priced in dollars and settled in POL at the live rate when you buy. The full ladder with live pricing is on the home page. Packages of $20 or more count toward qualification.' },
|
||||||
{ re: /(safe|trust|rug|run away|company disappear)/i,
|
{ re: /(safe|trust|rug|run away|company disappear)/i,
|
||||||
@@ -46,7 +46,7 @@ function systemPrompt() {
|
|||||||
return `You are the assistant on InstantAdPay (https://instantadpay.com), a membership advertising platform.
|
return `You are the assistant on InstantAdPay (https://instantadpay.com), a membership advertising platform.
|
||||||
|
|
||||||
FACTS:
|
FACTS:
|
||||||
- Free to join with email only (6-digit code sign-in, no passwords). Wallet appears only at purchase or payout activation.
|
- Free to join with email only (6-digit code sign-in, no passwords). Wallet appears only at purchase or payout activation. Every new member gets a small welcome batch of ad credits at signup (engine-side bonus credits, separate from on-chain purchased credits).
|
||||||
- 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), login ads (per day). Coming: inbox ads, featured rotation with disclosed rotation size, verified-visit packs.
|
- Live formats: display banners (per impression), text ads (per impression), login ads (per day). Coming: inbox ads, featured rotation with disclosed rotation size, verified-visit packs.
|
||||||
- Every purchase is split by an immutable smart contract in the same transaction: 50% direct sponsor, 20% level 2, 10% level 3, 20% platform. No withdrawals exist; money lands in members' own wallets instantly.
|
- Every purchase is split by an immutable smart contract in the same transaction: 50% direct sponsor, 20% level 2, 10% level 3, 20% platform. No withdrawals exist; money lands in members' own wallets instantly.
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ async function bootstrap() {
|
|||||||
created BIGINT NOT NULL,
|
created BIGINT NOT NULL,
|
||||||
INDEX (owner_email), INDEX (type, status), INDEX (member_id)
|
INDEX (owner_email), INDEX (type, status), INDEX (member_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
||||||
|
await q(`CREATE TABLE IF NOT EXISTS earned_credits (
|
||||||
|
email VARCHAR(190) PRIMARY KEY,
|
||||||
|
balance INT NOT NULL DEFAULT 0,
|
||||||
|
granted_welcome TINYINT NOT NULL DEFAULT 0,
|
||||||
|
updated BIGINT NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
|
||||||
await q(`CREATE TABLE IF NOT EXISTS burns (
|
await q(`CREATE TABLE IF NOT EXISTS burns (
|
||||||
id VARCHAR(32) PRIMARY KEY,
|
id VARCHAR(32) PRIMARY KEY,
|
||||||
member_id INT NOT NULL,
|
member_id INT NOT NULL,
|
||||||
|
|||||||
+3
-1
@@ -30,7 +30,9 @@
|
|||||||
try {
|
try {
|
||||||
const d = await (await fetch('/api/my/dashboard')).json();
|
const d = await (await fetch('/api/my/dashboard')).json();
|
||||||
if (d.error) return;
|
if (d.error) return;
|
||||||
$('dbCredits').textContent = (d.credits || 0).toLocaleString();
|
const wc = d.welcomeCredits || 0;
|
||||||
|
$('dbCredits').textContent = ((d.credits || 0) + wc).toLocaleString();
|
||||||
|
$('dbCreditsSub').textContent = wc ? (d.credits || 0).toLocaleString() + ' purchased · ' + wc + ' welcome' : '';
|
||||||
$('dbEarned').textContent = IAP.fmtPol(d.earnedWei || '0');
|
$('dbEarned').textContent = IAP.fmtPol(d.earnedWei || '0');
|
||||||
$('dbBuyers').textContent = d.buyerCount || 0;
|
$('dbBuyers').textContent = d.buyerCount || 0;
|
||||||
$('dbTeam').textContent = (d.referrals || []).length;
|
$('dbTeam').textContent = (d.referrals || []).length;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<title>The contract | InstantAdPay</title>
|
<title>The contract | InstantAdPay</title>
|
||||||
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
||||||
<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=20260905a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260905b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -129,8 +129,8 @@
|
|||||||
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260905a"></script>
|
<script src="/assets/common.js?v=20260905b"></script>
|
||||||
<script src="/assets/contract.js?v=20260905a"></script>
|
<script src="/assets/contract.js?v=20260905b"></script>
|
||||||
<script src="/assets/chat.js?v=20260905a"></script>
|
<script src="/assets/chat.js?v=20260905b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+6
-5
@@ -5,7 +5,7 @@
|
|||||||
<title>InstantAdPay: advertise and earn, locked in code</title>
|
<title>InstantAdPay: advertise and earn, locked in code</title>
|
||||||
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
||||||
<link rel="stylesheet" href="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=20260905a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260905b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -255,6 +255,7 @@
|
|||||||
<h3>Free membership includes</h3>
|
<h3>Free membership includes</h3>
|
||||||
<ul class="checks">
|
<ul class="checks">
|
||||||
<li>A member account and the live ledger</li>
|
<li>A member account and the live ledger</li>
|
||||||
|
<li>Welcome credits to taste real ad delivery</li>
|
||||||
<li>Your personal referral link, working from day one</li>
|
<li>Your personal referral link, working from day one</li>
|
||||||
<li>Earnings from day one on your referrals' package purchases</li>
|
<li>Earnings from day one on your referrals' package purchases</li>
|
||||||
<li>Access to the member dashboard</li>
|
<li>Access to the member dashboard</li>
|
||||||
@@ -398,9 +399,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="/assets/common.js?v=20260905a"></script>
|
<script src="/assets/common.js?v=20260905b"></script>
|
||||||
<script src="/assets/wallet.js?v=20260905a"></script>
|
<script src="/assets/wallet.js?v=20260905b"></script>
|
||||||
<script src="/assets/home.js?v=20260905a"></script>
|
<script src="/assets/home.js?v=20260905b"></script>
|
||||||
<script src="/assets/chat.js?v=20260905a"></script>
|
<script src="/assets/chat.js?v=20260905b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-4
@@ -5,7 +5,7 @@
|
|||||||
<title>Live ledger | InstantAdPay</title>
|
<title>Live ledger | InstantAdPay</title>
|
||||||
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
||||||
<link rel="stylesheet" href="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=20260905a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260905b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260905a"></script>
|
<script src="/assets/common.js?v=20260905b"></script>
|
||||||
<script src="/assets/ledger.js?v=20260905a"></script>
|
<script src="/assets/ledger.js?v=20260905b"></script>
|
||||||
<script src="/assets/chat.js?v=20260905a"></script>
|
<script src="/assets/chat.js?v=20260905b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+6
-6
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>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="stylesheet" href="/assets/site.css?v=20260905a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260905b">
|
||||||
</head>
|
</head>
|
||||||
<body class="bo-body">
|
<body class="bo-body">
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
<div class="pane" id="pane-overview">
|
<div class="pane" id="pane-overview">
|
||||||
<div class="stats" style="margin-top:0">
|
<div class="stats" style="margin-top:0">
|
||||||
<div class="stat"><div class="n" id="dbCredits">0</div><div class="l">Ad credits</div></div>
|
<div class="stat"><div class="n" id="dbCredits">0</div><div class="l">Ad credits</div><div class="small muted" id="dbCreditsSub"></div></div>
|
||||||
<div class="stat"><div class="n mono" id="dbEarned">0</div><div class="l">POL earned</div></div>
|
<div class="stat"><div class="n mono" id="dbEarned">0</div><div class="l">POL earned</div></div>
|
||||||
<div class="stat"><div class="n" id="dbBuyers">0</div><div class="l">Qualifying buyers</div></div>
|
<div class="stat"><div class="n" id="dbBuyers">0</div><div class="l">Qualifying buyers</div></div>
|
||||||
<div class="stat"><div class="n" id="dbTeam">0</div><div class="l">Joined your line</div></div>
|
<div class="stat"><div class="n" id="dbTeam">0</div><div class="l">Joined your line</div></div>
|
||||||
@@ -208,9 +208,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/assets/common.js?v=20260905a"></script>
|
<script src="/assets/common.js?v=20260905b"></script>
|
||||||
<script src="/assets/wallet.js?v=20260905a"></script>
|
<script src="/assets/wallet.js?v=20260905b"></script>
|
||||||
<script src="/assets/my.js?v=20260905a"></script>
|
<script src="/assets/my.js?v=20260905b"></script>
|
||||||
<script src="/assets/chat.js?v=20260905a"></script>
|
<script src="/assets/chat.js?v=20260905b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -316,7 +316,8 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const out = { memberId, email: s.email || (acct && acct.email) || null,
|
const out = { memberId, email: s.email || (acct && acct.email) || null,
|
||||||
address: s.address || (acct && acct.address) || null,
|
address: s.address || (acct && acct.address) || null,
|
||||||
refCode: (acct && acct.code) || null, credits: 0, buyerCount: 0,
|
refCode: (acct && acct.code) || null, credits: 0, buyerCount: 0,
|
||||||
earnedWei: '0', earnCount: 0, referrals: [] };
|
earnedWei: '0', earnCount: 0, referrals: [], welcomeCredits: 0 };
|
||||||
|
if (out.email) out.welcomeCredits = await ads.grantWelcome(out.email); // idempotent lazy grant
|
||||||
if (memberId) {
|
if (memberId) {
|
||||||
try {
|
try {
|
||||||
const mm = await chain.member(memberId);
|
const mm = await chain.member(memberId);
|
||||||
|
|||||||
Reference in New Issue
Block a user