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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user