Credit activity ledger: every earn and spend logged with a reason, listed under Campaigns

Hugh earned a 10-credit sign-in bonus, then bought a 280-credit extension; earned credits spend
first, so Earned read 0 and the bonus looked lost. There was no record a member could check.
Now every real credit movement writes a credit_log row (JSON store in file mode): welcome, sign-in
bonus with streak, daily claim, inbox reads, video watches, verified visits, milestone/leaderboard
bonuses, partner codes, admin/team grants, AI Copy Engine charges + refunds, flat buys (featured
run, extension, visit pack, more visits), login-ad days, and metered delivery rolled up per
campaign per day; on-chain purchased-credit spends are marked. GET /api/my/credits/activity;
dashboard shows the last 40 under Campaigns (also when there are no campaigns) with a link from
Earn credits. Chatbot canned answer + AI prompt updated; my.js tag bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-16 08:41:43 -05:00
parent ffacf741da
commit 69d672665c
8 changed files with 50 additions and 14 deletions
+1 -1
View File
@@ -111,7 +111,7 @@ async function rolloverTick() {
const prize = prizeText(kind, sc);
const rec = { kind, start: prevStart, end: cur, prize: prize || '', ladder: lad, top: top.map(pub), granted: [], at: now };
w.unshift(rec); changed = true;
for (let i = 0; i < lad.length && i < top.length; i++) { try { await R.ads.addEarned(top[i].email, lad[i]); rec.granted.push({ rank: i + 1, name: top[i].name, credits: lad[i] }); } catch (e) {} }
for (let i = 0; i < lad.length && i < top.length; i++) { try { await R.ads.addEarned(top[i].email, lad[i], { log: { kind: 'bonus', note: 'Leaderboard prize: rank ' + (i + 1) + ' (' + kind + ')' } }); rec.granted.push({ rank: i + 1, name: top[i].name, credits: lad[i] }); } catch (e) {} }
if (top[0]) {
const when = new Date(prevStart).toLocaleDateString('en-US', { timeZone: TZ, month: 'short', day: 'numeric' });
const line = '\u{1F3C6} <b>InstantAdPay</b> · ' + (kind === 'week' ? 'Weekly' : 'Monthly') + ' referral contest (from ' + when + '): <b>' + top[0].name + '</b> wins with ' + top[0].sales + ' package' + (top[0].sales === 1 ? '' : 's') + ' sold ($' + top[0].usd + ')' + (top[1] ? ' · 2nd ' + top[1].name + ' ($' + top[1].usd + ')' : '') + (top[2] ? ' · 3rd ' + top[2].name + ' ($' + top[2].usd + ')' : '') + (rec.granted.length ? '\nCredits awarded: ' + rec.granted.map(g => g.name + ' +' + g.credits.toLocaleString()).join(', ') : (prize ? '\nPrize: ' + prize : '')) + '\ninstantadpay.com/leaderboard';