P&L: account for money the chain cannot explain
The P&L took revenue from AdminPaid events and holdings from eth_getBalance, so anything else moving through a receiver was invisible. Receiver A has earned 10,871.89 POL and holds 4,561.19; nothing explained the difference, and the gap would have widened every time Marty drew from it. "Why would I ever keep a receiver wallet that I never take anything out of? That's stupid, so I need to be able to account for it." Three kinds, because three different things move through that wallet and only one is profit. Revenue is outside income, a ClickBaitPays withdrawal paid in POL, and counts toward profit. A draw is house profit spent on something, such as funding RM Circle #139's Culmen to Apex upgrade; it leaves the wallet but is not a cost of running InstantAdPay. A transfer is Marty's own capital parked in the Tangem receiver for safekeeping, and moves the balance only: counting it as revenue would report his savings as earnings. The pane now states earned, plus revenue, less draws, against what is actually held, and names any unexplained drift rather than hiding it in a balance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+516
-498
File diff suppressed because it is too large
Load Diff
@@ -739,10 +739,63 @@
|
||||
const W = r.wallets || {}, B = r.balances || {};
|
||||
$('pnlWallets').innerHTML = '<tr><th>Wallet</th><th>Address</th><th>Balance</th></tr>'
|
||||
+ [['Owner / fee A (Tangem)', W.feeA, B.feeA], ['Fee B', W.feeB, B.feeB], ['Engine (gas)', W.engine, B.engine]].filter(x => x[1]).map(x => '<tr><td>' + x[0] + '</td><td class="mono small">' + esc(x[1]) + '</td><td class="mono">' + (x[2] == null ? '?' : pol(x[2]) + ' POL') + '</td></tr>').join('');
|
||||
renderLedger(r);
|
||||
$('pnlFixed').value = r.fixedMonthlyUsd || 0;
|
||||
const b = r.burner || {};
|
||||
$('burnerLine').textContent = !b.hasEthers ? 'ethers is not installed in this build.' : !b.keyPresent ? 'No engine key configured (ENGINE_KEY). Burns stay pending until it is set.' : b.mismatch ? 'ENGINE_KEY does not match the contract engine signer. Disabled.' : 'Engine wallet ' + b.address + ' holds ' + pol(b.balanceWei) + ' POL. That is its gas fund, not a cost: one burn uses about 0.003 POL (roughly 48,000 gas), paid by this wallet, never by the member. ' + b.burned + ' burn' + (b.burned === 1 ? '' : 's') + ' since boot' + (b.lastRun ? ' · last check ' + when(b.lastRun) : '') + (b.lastError ? ' · last error: ' + b.lastError : '') + (b.skipped && Object.keys(b.skipped).length ? ' · skipped (needs review): ' + Object.entries(b.skipped).map(([k, v]) => k + ' (' + v + ')').join(', ') : '');
|
||||
}
|
||||
// The ledger: hand-entered movements the chain index cannot see, plus the reconciliation that
|
||||
// makes "earned" and "on hand" agree. A drift that will not go to zero is a transfer nobody
|
||||
// wrote down (Marty, 2026-09-23).
|
||||
const LD_KIND = { revenue: 'Revenue', draw: 'Draw', transfer: 'Transfer' };
|
||||
const LD_WALLET = { feeA: 'Receiver A', feeB: 'Receiver B', engine: 'Engine', hunt: 'PolHunter' };
|
||||
const pol4 = n => Number(n || 0).toLocaleString(undefined, { maximumFractionDigits: 4 });
|
||||
function renderLedger(r) {
|
||||
const L = (r && r.ledger) || {}, rows = L.entries || [], t = L.totals || {}, rec = L.reconcile || {};
|
||||
if ($('pnlLedger')) {
|
||||
$('pnlLedger').innerHTML = '<tr><th>Date</th><th>Wallet</th><th>Kind</th><th>POL</th><th>What for</th><th></th></tr>'
|
||||
+ (rows.length ? rows.map(e => '<tr><td>' + e.date + '</td><td>' + (LD_WALLET[e.wallet] || e.wallet) + '</td>'
|
||||
+ '<td>' + (LD_KIND[e.kind] || e.kind) + '</td>'
|
||||
+ '<td' + (e.dir === 'out' ? ' style="color:var(--bad)"' : '') + '>' + (e.dir === 'out' ? '-' : '+') + pol4(e.amount)
|
||||
+ (e.usd ? ' <span class="muted">($' + Number(e.usd).toFixed(2) + ')</span>' : '') + '</td>'
|
||||
+ '<td>' + esc(e.note || '') + '</td>'
|
||||
+ '<td><button class="btn small sec" type="button" data-ldkill="' + e.id + '">Remove</button></td></tr>').join('')
|
||||
: '<tr><td colspan="6" class="muted">Nothing recorded yet.</td></tr>');
|
||||
$('pnlLedger').querySelectorAll('[data-ldkill]').forEach(b => b.addEventListener('click', async () => {
|
||||
if (!await IAP.confirmBox('Remove this ledger line?')) return;
|
||||
try { await api('/api/admin/ledger/delete', { id: b.dataset.ldkill }); loadPnl(); }
|
||||
catch (e) { IAP.status('Could not remove it: ' + e.message, 'bad'); }
|
||||
}));
|
||||
}
|
||||
if ($('pnlRecon')) {
|
||||
const drift = Number(rec.drift || 0);
|
||||
const ok = Math.abs(drift) < 0.01;
|
||||
$('pnlRecon').innerHTML = 'Receiver A earned <b>' + pol4(rec.earnedPol) + ' POL</b> on chain'
|
||||
+ (t.revenuePol ? ' plus <b>' + pol4(t.revenuePol) + '</b> outside revenue' : '')
|
||||
+ (t.drawPol ? ', less <b>' + pol4(t.drawPol) + '</b> drawn' : '')
|
||||
+ (t.transferPol ? ', ' + (t.transferPol > 0 ? 'plus ' : 'less ') + '<b>' + pol4(Math.abs(t.transferPol)) + '</b> transferred in for storage' : '')
|
||||
+ '. Expected <b>' + pol4(rec.expected) + '</b>, actually holding <b>' + pol4(rec.actual) + '</b>. '
|
||||
+ (ok ? '<span style="color:var(--mint)">Reconciled.</span>'
|
||||
: '<span style="color:var(--amber)">Unexplained: ' + pol4(drift) + ' POL</span> '
|
||||
+ '<span class="muted">(' + (drift > 0 ? 'more on hand than accounted for, so income or a transfer in is missing a line'
|
||||
: 'less on hand than accounted for, so a draw is missing a line') + ')</span>');
|
||||
}
|
||||
}
|
||||
if ($('ldAdd')) $('ldAdd').addEventListener('click', async () => {
|
||||
const body = { dir: $('ldDir').value, kind: $('ldKind').value, wallet: $('ldWallet').value,
|
||||
amount: Number($('ldAmount').value) || 0, usd: Number($('ldUsd').value) || 0,
|
||||
date: $('ldDate').value, note: $('ldNote').value };
|
||||
try {
|
||||
await api('/api/admin/ledger', body);
|
||||
$('ldAmount').value = ''; $('ldUsd').value = ''; $('ldNote').value = '';
|
||||
IAP.status('Line added.', 'good'); loadPnl();
|
||||
} catch (e) { IAP.status(e.message, 'bad'); }
|
||||
});
|
||||
// a draw is almost never revenue and an outside deposit almost never a draw: move the kind with
|
||||
// the direction, but leave it editable
|
||||
if ($('ldDir')) $('ldDir').addEventListener('change', () => {
|
||||
$('ldKind').value = $('ldDir').value === 'in' ? 'revenue' : 'draw';
|
||||
});
|
||||
document.querySelectorAll('#pnlPeriods [data-days]').forEach(b => b.addEventListener('click', () => { pnlDays = Number(b.dataset.days); document.querySelectorAll('#pnlPeriods [data-days]').forEach(x => x.classList.toggle('on', x === b)); loadPnl().catch(e => IAP.status(e.message, 'bad')); }));
|
||||
if ($('pnlFixedSave')) $('pnlFixedSave').addEventListener('click', async () => { try { await api('/api/admin/site', { pnlFixedMonthlyUsd: Number($('pnlFixed').value) || 0 }, 'PATCH'); IAP.status('Saved.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
|
||||
if ($('burnerRun')) $('burnerRun').addEventListener('click', async () => { try { const r = await api('/api/admin/burner/run', {}); IAP.status('Burner ran: ' + (r.burned || 0) + ' burned.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
|
||||
|
||||
Reference in New Issue
Block a user