Classify income rows as entry vs upgrade instead of raw atLevel
The contract books income atLevel = the level the payer upgraded OUT of (upgrade N->N+1 records atLevel=N), so an Ascensus upgrade pass-up was displaying as "Scintilla". Income rows are now classified against the contract's getAllCosts() tables — upgrade pass-ups arrive at full upgrade cost, entry referral rewards at slot cost minus the 5% admin fee — and display "Upgrade to Ascensus" / "Entry — Scintilla" in the admin income table, public feed, and toasts. Stale snapshot rows re-classify via a forced re-snapshot on boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,8 @@ const SEL = {
|
||||
totalMembers: '0x76e92559', // totalMembers()
|
||||
getIncomeHistory: '0x765d1209', // getIncomeHistory(uint48)
|
||||
getDirectReferrals: '0xcf00a645', // getDirectReferrals(uint48)
|
||||
getMatrixChildren: '0x04c8cc3d' // getMatrixChildren(uint48)
|
||||
getMatrixChildren: '0x04c8cc3d', // getMatrixChildren(uint48)
|
||||
getAllCosts: '0x735f87b9' // getAllCosts()
|
||||
};
|
||||
const CHUNK = 9000; // publicnode getLogs range cap is 10k
|
||||
const TAIL_MAX_BEHIND = 60000; // never tail further back than ~1.5 days (log pruning)
|
||||
@@ -52,6 +53,8 @@ function loadState() {
|
||||
if (!state || state.v !== 2) {
|
||||
state = { v: 2, lastBlock: 0, snapshotAt: 0, members: {}, payouts: [], totals: { count: 0, pol: 0 }, updatedAt: null };
|
||||
}
|
||||
// income rows saved before desc-classification existed: re-snapshot to backfill
|
||||
if (state.payouts.some(p => p.kind === 'income' && !p.desc)) state.snapshotAt = 0;
|
||||
}
|
||||
function saveState() {
|
||||
try {
|
||||
@@ -126,12 +129,34 @@ async function fetchIncome(id) {
|
||||
function levelName(n) { return LEVELS[n - 1] || `Level ${n}`; }
|
||||
function tierName(n) { return TIERS[n] || `Tier ${n}`; }
|
||||
|
||||
// Cost tables from getAllCosts(): standard[8], premium[8], stdUpgrade[8], premUpgrade[8].
|
||||
let costs = null;
|
||||
async function fetchCosts() {
|
||||
if (costs) return costs;
|
||||
const r = await ethCall(SEL.getAllCosts);
|
||||
const arr = i => Array.from({ length: 8 }, (_, j) => pol(wBig(r, i * 8 + j)));
|
||||
costs = { reg: { 1: arr(0), 2: arr(1) }, up: { 1: arr(2), 2: arr(3) } };
|
||||
return costs;
|
||||
}
|
||||
// The contract records income `atLevel` = the level the payer upgraded OUT of
|
||||
// (upgrade N→N+1 books atLevel=N), so raw atLevel reads misleadingly low.
|
||||
// Classify by amount against the cost tables: upgrade pass-ups arrive at FULL
|
||||
// upgrade cost; registration referral rewards arrive at slot cost minus the 5%
|
||||
// admin fee.
|
||||
function describeIncome(fromTier, atLevel, amountPol) {
|
||||
if (!costs || !costs.up[fromTier] || atLevel < 1 || atLevel > 8) return levelName(atLevel);
|
||||
if (Math.abs(amountPol - costs.up[fromTier][atLevel - 1]) < 0.02) return `Upgrade to ${levelName(atLevel + 1)}`;
|
||||
if (Math.abs(amountPol - costs.reg[fromTier][atLevel - 1] * 0.95) < 0.02) return `Entry — ${levelName(atLevel)}`;
|
||||
return levelName(atLevel);
|
||||
}
|
||||
|
||||
function samePayout(a, b) {
|
||||
return a.toId === b.toId && a.fromId === b.fromId && Math.abs(a.pol - b.pol) < 1e-6 && Math.abs((a.ts || 0) - (b.ts || 0)) <= 5;
|
||||
}
|
||||
|
||||
// full-history snapshot straight from contract storage (no logs needed)
|
||||
async function snapshot() {
|
||||
await fetchCosts().catch(() => {});
|
||||
const total = wInt(await ethCall(SEL.totalMembers), 0);
|
||||
const members = {}, history = [];
|
||||
for (let id = 1; id <= total; id++) {
|
||||
@@ -142,7 +167,7 @@ async function snapshot() {
|
||||
try { const r = await ethCall(SEL.getMatrixChildren + encU(id)); members[id].l = wInt(r, 0) || 0; members[id].r = wInt(r, 1) || 0; } catch (e) {}
|
||||
let inc = [];
|
||||
try { inc = await fetchIncome(id); } catch (e) { inc = []; }
|
||||
inc.forEach((p, i) => history.push({ key: `h${id}-${i}`, kind: 'income', toId: id, fromId: p.fromId, level: p.level, pol: p.pol, ts: p.ts }));
|
||||
inc.forEach((p, i) => history.push({ key: `h${id}-${i}`, kind: 'income', toId: id, fromId: p.fromId, level: p.level, pol: p.pol, ts: p.ts, desc: describeIncome(p.fromTier, p.level, p.pol) }));
|
||||
await new Promise(r => setTimeout(r, 60));
|
||||
}
|
||||
history.sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
||||
@@ -297,7 +322,7 @@ function getPayoutsPublic() {
|
||||
contract: CONTRACT,
|
||||
payouts: recent.map(p => ({
|
||||
key: p.key, kind: p.kind, toId: p.toId, fromId: p.fromId,
|
||||
level: p.level, levelName: levelName(p.level), pol: p.pol, tx: p.tx, ts: p.ts,
|
||||
level: p.level, levelName: levelName(p.level), pol: p.pol, tx: p.tx, ts: p.ts, desc: p.desc,
|
||||
toAccount: state.members[p.toId] ? state.members[p.toId].account : undefined,
|
||||
upgrade: p.upgrade ? { id: p.upgrade.id, newLevel: p.upgrade.newLevel, levelName: levelName(p.upgrade.newLevel), tier: tierName(p.upgrade.tier) } : undefined,
|
||||
passed: p.passed && p.passed.length ? p.passed : undefined
|
||||
@@ -340,8 +365,9 @@ async function memberLookup(id) {
|
||||
out.matrix = { left: wInt(r, 0) || null, right: wInt(r, 1) || null };
|
||||
} catch (e) { out.matrix = null; }
|
||||
try {
|
||||
await fetchCosts().catch(() => {});
|
||||
const inc = await fetchIncome(id);
|
||||
out.income = inc.map(p => ({ ...p, levelName: levelName(p.level) })).reverse();
|
||||
out.income = inc.map(p => ({ ...p, levelName: levelName(p.level), desc: describeIncome(p.fromTier, p.level, p.pol) })).reverse();
|
||||
} catch (e) { out.income = null; }
|
||||
return out;
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ document.getElementById('lookupForm').addEventListener('submit',async e=>{
|
||||
`<div class="fact"><small>Total earned</small><strong style="color:var(--ok)">${fmt(d.totalEarnedPol)} POL</strong></div>`+
|
||||
`<div class="fact"><small>Total paid in</small><strong>${fmt(d.totalPaidPol)} POL</strong></div></div>`;
|
||||
const chain=d.uplineChain&&d.uplineChain.length?`<p style="font-size:13px;color:var(--muted);margin:12px 0 0;line-height:1.7"><strong style="color:var(--text)">Lineage up to root:</strong> #${d.id} → ${d.uplineChain.map(u=>`#${u.id} <span style="color:#8498aa">(${esc(u.levelName)}, ${u.directCount} directs)</span>`).join(' → ')}</p>`:'';
|
||||
const income=d.income&&d.income.length?`<div class="table-wrap" style="margin-top:8px"><table class="table" style="min-width:560px"><thead><tr><th>When (UTC)</th><th>From member</th><th>At level</th><th>Tier</th><th>Amount</th></tr></thead><tbody>${d.income.map(p=>`<tr><td>${date(p.ts)}</td><td>#${p.fromId}</td><td>${esc(p.levelName)}</td><td>${p.fromTier===2?'Premium':'Standard'}</td><td><strong>${fmt(p.pol)} POL</strong></td></tr>`).join('')}</tbody></table></div>`:'<div class="empty" style="margin-top:8px">No payments received yet.</div>';
|
||||
const income=d.income&&d.income.length?`<div class="table-wrap" style="margin-top:8px"><table class="table" style="min-width:560px"><thead><tr><th>When (UTC)</th><th>From member</th><th>For</th><th>Tier</th><th>Amount</th></tr></thead><tbody>${d.income.map(p=>`<tr><td>${date(p.ts)}</td><td>#${p.fromId}</td><td>${esc(p.desc||p.levelName)}</td><td>${p.fromTier===2?'Premium':'Standard'}</td><td><strong>${fmt(p.pol)} POL</strong></td></tr>`).join('')}</tbody></table></div>`:'<div class="empty" style="margin-top:8px">No payments received yet.</div>';
|
||||
out.innerHTML=facts+chain+`<p style="font-size:12px;color:#8498aa;margin:14px 0 0">Payments received by #${d.id} — ${d.income?d.income.length:0} total, newest first:</p>`+income;
|
||||
}catch(x){out.innerHTML=`<div class="empty" style="color:var(--danger)">${esc(x.message)}</div>`}
|
||||
});
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@
|
||||
:`💸 Member #${p.toId} just got paid`;
|
||||
const sub=p.kind==='upline'
|
||||
?`${fmtPol(p.pol)} POL paid up to Member #${p.toId}`
|
||||
:(p.kind==='referral'?`+${fmtPol(p.pol)} POL · direct referral reward`:`+${fmtPol(p.pol)} POL · ${p.levelName||'level'} payout`);
|
||||
:(p.kind==='referral'?`+${fmtPol(p.pol)} POL · direct referral reward`:`+${fmtPol(p.pol)} POL · ${p.desc||p.levelName||'payout'}`);
|
||||
el.innerHTML=`<span class="pp-toast-head">${head}</span><span class="pp-toast-sub">${esc(sub)}</span><span class="pp-toast-verify">${timeAgo(p.ts)} · Verify on Polygonscan ↗</span>`;
|
||||
getStack().appendChild(el);
|
||||
requestAnimationFrame(()=>el.classList.add('show'));
|
||||
@@ -52,7 +52,7 @@
|
||||
const passed=p.passed&&p.passed.length?`<div class="pp-passed">passed over ${p.passed.map(i=>'#'+i).join(', ')} (not yet at this level)</div>`:'';
|
||||
return `<div class="pp-row"><div class="pp-icon">🚀</div><div class="pp-body"><strong>${up}${fmtPol(p.pol)} POL paid up to Member #${p.toId}</strong><div class="pp-meta">Upgrade pass-up · ${esc(p.levelName)} · ${when}</div>${passed}</div>${verify}</div>`;
|
||||
}
|
||||
const meta=p.kind==='referral'?`Direct referral reward from #${p.fromId}'s entry`:`${esc(p.levelName)} payout from #${p.fromId}`;
|
||||
const meta=p.kind==='referral'?`Direct referral reward from #${p.fromId}'s entry`:`${esc(p.desc||p.levelName)} — from #${p.fromId}`;
|
||||
return `<div class="pp-row"><div class="pp-icon">💸</div><div class="pp-body"><strong>Member #${p.toId} received ${fmtPol(p.pol)} POL</strong><div class="pp-meta">${meta} · ${when}</div></div>${verify}</div>`;
|
||||
}
|
||||
function render(d){
|
||||
|
||||
Reference in New Issue
Block a user