Earn-and-spend credit economy + private rehearsal chain cutover
Members earn credits by attention (spec 8b): daily 5-ad set with dwell timing and a too-fast guard, then a claimable daily batch. Earned credits now FUND campaigns: banner/text budgets draw earned-first (free members can advertise on welcome credits alone), purchased credits and the on-chain burn queue only cover the remainder; earned-only campaigns pause when the pool runs dry. New Earn credits section in the member menu with the viewer. Explorer links degrade gracefully for the private chain (contract page points at the audited Amoy verification). Site flipped to the anvil rehearsal chain at rpc.instantadpay.com: unlimited test POL, no more faucets. E2E: welcome->views->claim->earned-funded campaign->charged serving, all green. Assets v=20260905d. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -345,6 +345,23 @@ const server = http.createServer(async (req, res) => {
|
||||
}));
|
||||
return json(res, 200, out);
|
||||
}
|
||||
// -- earn credits by viewing ads (attention-gated daily claim)
|
||||
if (p === '/api/my/earn' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
return json(res, 200, await ads.viewStatus(s.email));
|
||||
}
|
||||
if (p === '/api/my/adview' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
return json(res, 200, await ads.recordView(s.email));
|
||||
}
|
||||
if (p === '/api/my/claim' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const r = await ads.claimDaily(s.email);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
if (p === '/api/my/activity' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s) return json(res, 401, { error: 'Sign in first.' });
|
||||
@@ -376,14 +393,15 @@ const server = http.createServer(async (req, res) => {
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const memberId = await auth.refreshMemberId(s);
|
||||
const out = { campaigns: await ads.listCampaigns(s.email), rates: ads.rates() };
|
||||
out.availableCredits = memberId ? await ads.availableCredits(memberId) : 0;
|
||||
out.purchasedCredits = memberId ? await ads.availableCredits(memberId) : 0;
|
||||
out.earnedCredits = await ads.earnedBalance(s.email);
|
||||
out.availableCredits = out.purchasedCredits + out.earnedCredits;
|
||||
return json(res, 200, out);
|
||||
}
|
||||
if (p === '/api/my/campaigns' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const memberId = await auth.refreshMemberId(s);
|
||||
if (!memberId) return json(res, 400, { error: 'Buy an ad package first. Campaigns spend the on-chain credits it mints.' });
|
||||
const memberId = await auth.refreshMemberId(s); // 0 is fine: earned credits fund banner/text
|
||||
const b = await readBody(req);
|
||||
const r = await ads.createCampaign(s.email, memberId, b);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
|
||||
Reference in New Issue
Block a user