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:
@@ -85,7 +85,9 @@ window.IAP = (function () {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'row t-' + ev.type;
|
||||
div.innerHTML = '<span>' + describeEvent(ev, c) + '</span>'
|
||||
+ '<span class="tx"><a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + ev.tx + '">verify ↗</a></span>';
|
||||
+ (c.explorer
|
||||
? '<span class="tx"><a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + ev.tx + '">verify ↗</a></span>'
|
||||
: '<span class="tx mono">' + ev.tx.slice(0, 12) + '…</span>');
|
||||
return div;
|
||||
}
|
||||
// Render one served ad into #<elId>. Silent if no inventory.
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
$('cAddr').textContent = c.contract.slice(0, 10) + '…' + c.contract.slice(-6);
|
||||
$('cChain').textContent = c.chainName;
|
||||
$('mockAddr').textContent = c.contract.slice(0, 18) + '…';
|
||||
$('lnkExplorer').href = c.explorer + '/address/' + c.contract + '#code';
|
||||
const src = 'https://repo.sourcify.dev/contracts/full_match/' + c.chainId + '/' + c.contract + '/';
|
||||
$('lnkSource').href = src;
|
||||
$('lnkSource2').href = src;
|
||||
if (c.explorer) {
|
||||
$('lnkExplorer').href = c.explorer + '/address/' + c.contract + '#code';
|
||||
const src = 'https://repo.sourcify.dev/contracts/full_match/' + c.chainId + '/' + c.contract + '/';
|
||||
$('lnkSource').href = src;
|
||||
$('lnkSource2').href = src;
|
||||
} else {
|
||||
// private rehearsal chain: point at the audited Amoy verification instead
|
||||
const src = 'https://repo.sourcify.dev/contracts/full_match/80002/0xaF47EC401A9B0D570a553b1A1c95D6079083636e/';
|
||||
$('lnkExplorer').href = src;
|
||||
$('lnkSource').href = src;
|
||||
$('lnkSource2').href = src;
|
||||
}
|
||||
})();
|
||||
|
||||
+63
-4
@@ -68,9 +68,9 @@
|
||||
}
|
||||
|
||||
// ── back-office menu: hash-routed panes ───────────────
|
||||
const PANES = ['overview', 'line', 'campaigns', 'earnings', 'wallet'];
|
||||
const PANES = ['overview', 'line', 'campaigns', 'earn', 'earnings', 'wallet'];
|
||||
const TITLES = { overview: 'Overview', line: 'My line', campaigns: 'Campaigns',
|
||||
earnings: 'Earnings', wallet: 'Wallet & account' };
|
||||
earn: 'Earn credits', earnings: 'Earnings', wallet: 'Wallet & account' };
|
||||
function setPane(name) {
|
||||
if (!PANES.includes(name)) name = 'overview';
|
||||
for (const p of PANES) {
|
||||
@@ -113,8 +113,9 @@
|
||||
$('linkCard').hidden = !!me.address;
|
||||
$('activateCard').hidden = !(me.address && !me.memberId);
|
||||
$('activityArea').hidden = !me.memberId;
|
||||
$('campaignCard').hidden = !me.memberId;
|
||||
if (me.memberId) loadCampaigns();
|
||||
$('campGate').hidden = true; // earned credits fund campaigns for everyone
|
||||
$('campaignCard').hidden = false;
|
||||
loadCampaigns();
|
||||
|
||||
// the share link works from day one; codes resolve to your chain id later
|
||||
if (me.refCode || me.memberId) {
|
||||
@@ -159,6 +160,7 @@
|
||||
const r = await (await fetch('/api/my/campaigns')).json();
|
||||
if (r.error) return;
|
||||
$('rateLine').textContent = 'Available to spend: ' + r.availableCredits.toLocaleString()
|
||||
+ (r.earnedCredits ? ' (' + r.purchasedCredits.toLocaleString() + ' purchased + ' + r.earnedCredits + ' earned)' : '')
|
||||
+ ' credits · rates: banner ' + r.rates.bannerCreditsPerBatch + 'cr/' + r.rates.bannerBatch
|
||||
+ ' views, text ' + r.rates.textCreditsPerBatch + 'cr/' + r.rates.textBatch
|
||||
+ ' views, login ' + r.rates.loginCreditsPerDay + 'cr/day';
|
||||
@@ -200,6 +202,63 @@
|
||||
// defers the busy() lookup to click time (busy is declared below)
|
||||
function busy2(btn, fn) { return (...a) => busy(btn, fn)(...a); }
|
||||
|
||||
// ── earn-by-viewing: daily ad set with dwell, then claim ──
|
||||
const earnState = { types: ['banner', 'text'], i: 0, timer: null };
|
||||
async function earnRefresh() {
|
||||
try {
|
||||
const st = await (await fetch('/api/my/earn')).json();
|
||||
if (st.error) return null;
|
||||
$('earnProgress').textContent = 'today: ' + st.views + ' / ' + st.target + ' ads viewed';
|
||||
$('earnBalance').textContent = st.earned + ' earned credits';
|
||||
const done = st.views >= st.target;
|
||||
$('earnClaimBtn').hidden = !(done && !st.claimed);
|
||||
if (st.claimed) $('earnHint').textContent = 'Claimed for today. Come back tomorrow, or put those credits to work in Campaigns.';
|
||||
else if (done) $('earnHint').textContent = 'Set complete. Claim your ' + st.claimCredits + ' credits.';
|
||||
return st;
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
async function earnShowAd() {
|
||||
const st = await earnRefresh();
|
||||
if (!st || st.views >= st.target) return;
|
||||
const type = earnState.types[earnState.i++ % earnState.types.length];
|
||||
let ad = null;
|
||||
try { ad = (await (await fetch('/api/ads/slot?type=' + type)).json()).ad; } catch (e) {}
|
||||
const box = $('earnAdBox');
|
||||
if (!ad) {
|
||||
box.innerHTML = '<span class="muted small">No member ads are live in rotation right now. '
|
||||
+ 'Views resume the moment a campaign is running.</span>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = ad.imageUrl
|
||||
? '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow"><img src="' + ad.imageUrl + '" alt="member ad" style="max-height:200px"></a>'
|
||||
: '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow" style="font-size:17px"><b>' + ad.title + '</b>' + (ad.body ? '<br><span class="muted">' + ad.body + '</span>' : '') + '</a>';
|
||||
const btn = $('earnNextBtn');
|
||||
btn.hidden = false;
|
||||
btn.disabled = true;
|
||||
let left = st.dwell;
|
||||
btn.textContent = 'Next ad (' + left + 's)';
|
||||
clearInterval(earnState.timer);
|
||||
earnState.timer = setInterval(async () => {
|
||||
left -= 1;
|
||||
if (left > 0) { btn.textContent = 'Next ad (' + left + 's)'; return; }
|
||||
clearInterval(earnState.timer);
|
||||
btn.textContent = 'Next ad';
|
||||
btn.disabled = false;
|
||||
await fetch('/api/my/adview', { method: 'POST' }); // dwell served: count it
|
||||
await earnRefresh();
|
||||
}, 1000);
|
||||
}
|
||||
$('earnStartBtn').addEventListener('click', () => { $('earnStartBtn').hidden = true; earnShowAd(); });
|
||||
$('earnNextBtn').addEventListener('click', earnShowAd);
|
||||
$('earnClaimBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const r = await api('/api/my/claim');
|
||||
IAP.status('+' + r.credited + ' credits earned. Spend them in Campaigns.', 'ok');
|
||||
await earnRefresh();
|
||||
loadDashboard();
|
||||
} catch (e) { IAP.status(e.message, 'bad'); }
|
||||
});
|
||||
|
||||
const busy = (btn, fn) => async () => {
|
||||
try { btn.disabled = true; await fn(); }
|
||||
catch (e) { IAP.status((e && e.message) || String(e), 'bad'); }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<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.">
|
||||
<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=20260905b">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905b"></script>
|
||||
<script src="/assets/contract.js?v=20260905b"></script>
|
||||
<script src="/assets/chat.js?v=20260905b"></script>
|
||||
<script src="/assets/common.js?v=20260905d"></script>
|
||||
<script src="/assets/contract.js?v=20260905d"></script>
|
||||
<script src="/assets/chat.js?v=20260905d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5
-5
@@ -5,7 +5,7 @@
|
||||
<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.">
|
||||
<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=20260905b">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -399,9 +399,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="/assets/common.js?v=20260905b"></script>
|
||||
<script src="/assets/wallet.js?v=20260905b"></script>
|
||||
<script src="/assets/home.js?v=20260905b"></script>
|
||||
<script src="/assets/chat.js?v=20260905b"></script>
|
||||
<script src="/assets/common.js?v=20260905d"></script>
|
||||
<script src="/assets/wallet.js?v=20260905d"></script>
|
||||
<script src="/assets/home.js?v=20260905d"></script>
|
||||
<script src="/assets/chat.js?v=20260905d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@
|
||||
<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.">
|
||||
<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=20260905b">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905b"></script>
|
||||
<script src="/assets/ledger.js?v=20260905b"></script>
|
||||
<script src="/assets/chat.js?v=20260905b"></script>
|
||||
<script src="/assets/common.js?v=20260905d"></script>
|
||||
<script src="/assets/ledger.js?v=20260905d"></script>
|
||||
<script src="/assets/chat.js?v=20260905d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+27
-5
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<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="/assets/site.css?v=20260905b">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<button data-pane="overview" class="on" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="8" height="8" rx="2"/><rect x="13" y="3" width="8" height="8" rx="2"/><rect x="3" y="13" width="8" height="8" rx="2"/><rect x="13" y="13" width="8" height="8" rx="2"/></svg>Overview</button>
|
||||
<button data-pane="line" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="7" r="3.2"/><circle cx="5" cy="17" r="2.6"/><circle cx="19" cy="17" r="2.6"/><path d="M12 10v3M12 13l-5 2M12 13l5 2"/></svg>My line</button>
|
||||
<button data-pane="campaigns" type="button"><svg viewBox="0 0 24 24"><path d="M3 11l14-5v12L3 13v-2z"/><path d="M17 8a4 4 0 0 1 0 8M7 13v5a2 2 0 0 0 4 0v-3"/></svg>Campaigns</button>
|
||||
<button data-pane="earn" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v9M9 10c0-1.1 1.3-1.8 3-1.8s3 .7 3 1.8-1.3 1.6-3 1.8-3 .7-3 1.8 1.3 1.8 3 1.8 3-.7 3-1.8"/></svg>Earn credits</button>
|
||||
<button data-pane="earnings" type="button"><svg viewBox="0 0 24 24"><path d="M4 17l5-5 4 3 7-8"/><path d="M14 7h6v6"/></svg>Earnings</button>
|
||||
<button data-pane="wallet" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/><circle cx="16.5" cy="14.5" r="1.4"/></svg>Wallet</button>
|
||||
</nav>
|
||||
@@ -155,6 +156,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-earn" hidden>
|
||||
<div class="card">
|
||||
<h3>Earn credits by viewing ads</h3>
|
||||
<p class="muted small">View today's set of member ads, each for a few seconds, and claim your
|
||||
daily credits. Attention earns advertising here: your earned credits spend on banner and text
|
||||
campaigns, just like purchased ones.</p>
|
||||
<p><span class="badge" id="earnProgress">…</span>
|
||||
<span class="badge" id="earnBalance">…</span></p>
|
||||
<div class="card" style="background:rgba(4,8,7,.5)" id="earnStage">
|
||||
<div id="earnAdBox" class="small muted" style="min-height:120px;display:grid;place-items:center">
|
||||
Press start to see the first ad.</div>
|
||||
<p style="margin-top:14px">
|
||||
<button class="btn" id="earnStartBtn" type="button">Start viewing</button>
|
||||
<button class="btn" id="earnNextBtn" type="button" hidden disabled>Next ad</button>
|
||||
<button class="btn" id="earnClaimBtn" type="button" hidden>Claim today's credits</button>
|
||||
</p>
|
||||
<p class="small muted" id="earnHint"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-earnings" hidden>
|
||||
<div id="activityArea" hidden>
|
||||
<div class="grid c2">
|
||||
@@ -208,9 +230,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260905b"></script>
|
||||
<script src="/assets/wallet.js?v=20260905b"></script>
|
||||
<script src="/assets/my.js?v=20260905b"></script>
|
||||
<script src="/assets/chat.js?v=20260905b"></script>
|
||||
<script src="/assets/common.js?v=20260905d"></script>
|
||||
<script src="/assets/wallet.js?v=20260905d"></script>
|
||||
<script src="/assets/my.js?v=20260905d"></script>
|
||||
<script src="/assets/chat.js?v=20260905d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user