// 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} ' + who(pur.buyerId) + ' bought a ' + usd(pur.priceCents) + ' package']; 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 + ': ' + weiToPol(t.amountWei) + ' POL' + about + ' → ' + who(t.recipientId) + (t.hops > 0 ? ' (passed up ' + t.hops + ')' : '')); } if (!tiers.length) lines.push('No member payout on this one: the buyer has nobody above them to pay.'); 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 };