Proof feed: one message per purchase, and it says InstantAdPay

Marty, reading the feed on 25 September: "I see the level 1 payout, I see
a level 2 payout, but I don't see who purchased something and what they
bought." And in the main Crypto Team Build group the same lines carried no
product name at all, so nobody knew what they were from.

feedgroup.js composes one message from every event of a purchase
transaction: the buyer and the package on top, the level payouts under it
with their dollar equivalents, then who was passed over and why. It is
triggered on AdminPaid, the last event the contract emits for a purchase,
which is how the missed-payout notice already knows the whole transaction
is indexed. Tier payouts and purchases are no longer posted one line at a
time. In payouts mode a purchase that paid no member (root or unsponsored
buyer) stays silent, as before. Every post now opens with the InstantAdPay
header in the main group as well as the payments topic.

Composed against three real transactions from the live index before
shipping: three tiers, a double pass-up, and a root purchase. QA member
walk: 0 bugs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-25 07:02:59 -05:00
parent 742c271469
commit d2a3f162d0
2 changed files with 56 additions and 6 deletions
+39
View File
@@ -0,0 +1,39 @@
// One proof-feed message per purchase (Marty, 2026-09-25).
//
// The feed used to post each tier payout on its own line and, in payouts mode, never said who
// bought what. Marty: "I see the level 1 payout, I see a level 2 payout, but I don't see who
// purchased something and what they bought." So: the buyer and the package first, then the
// level payouts under it, then any pass-ups, as ONE message. It composes from every event of
// the transaction, which is why the caller triggers it on AdminPaid, the last event the contract
// emits for a purchase: by then Purchase, TierPaid and PassedUp of that tx are all indexed.
//
// Pure: events in, HTML string out. Tested against real index rows before it shipped.
'use strict';
const PCT = { 1: 50, 2: 20, 3: 10 };
function usd(cents) { return ('$' + (cents / 100).toFixed(2)).replace(/\.00$/, ''); }
// txEvents: every indexed event with this tx. who(id) -> "#12 @name". weiToPol(wei) -> "1.23".
// Returns null when there is no Purchase in the tx (not a package purchase) or when
// `requirePayout` is set and no member was paid (a root or unsponsored buyer: nothing to prove).
function compose(txEvents, who, weiToPol, opts) {
opts = opts || {};
const pur = txEvents.find(e => e.type === 'Purchase');
if (!pur) return null;
const tiers = txEvents.filter(e => e.type === 'TierPaid').sort((a, b) => (a.tier || 0) - (b.tier || 0) || (a.li || 0) - (b.li || 0));
if (opts.requirePayout && !tiers.length) return null;
const lines = ['\u{1F9FE} <b>' + who(pur.buyerId) + '</b> bought a <b>' + usd(pur.priceCents) + ' package</b>'];
for (const t of tiers) {
const pct = PCT[t.tier];
const about = pct ? ' (' + usd(Math.round(pur.priceCents * pct / 100)) + ')' : '';
lines.push('\u{1F4B8} Level ' + t.tier + ': <b>' + weiToPol(t.amountWei) + ' POL</b>' + about + ' → ' + who(t.recipientId)
+ (t.hops > 0 ? ' <i>(passed up ' + t.hops + ')</i>' : ''));
}
if (!tiers.length) lines.push('<i>No member payout on this one: the buyer has nobody above them to pay.</i>');
const passed = txEvents.filter(e => e.type === 'PassedUp');
for (const p of passed) if (p.skippedId) lines.push('⏭ Level ' + (p.tier || '?') + ' skipped ' + who(p.skippedId) + ' (not yet qualified)');
return lines.join('\n');
}
module.exports = { compose };