Payments feeds: post who missed a share and who got it (on-chain pass-ups and off-chain walk-ups)

Marty, 2026-09-16: make missing a payout visible. On a TierPaid with hops>0 the PassedUp events of the
same tx name the skipped positions, with the reason and how many qualifying buyers they had; the post
says the amount and who received it instead. On a Purchase whose buyer was routed past an unactivated
sponsor (movedByTx set by sponsorSyncOnEvent), the post names the sponsor who missed the whole sale
and the upline who now owns that buyer for good. Both go to the main feed and the shared payments
topic unless the events mode is 'none'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-16 13:25:31 -05:00
parent 230c250c64
commit b811b96e33
+37 -1
View File
@@ -619,6 +619,8 @@ async function sponsorSyncOnEvent(ev) {
const ref = spAcct && spAcct.code ? spAcct.code : String(onchain);
const old = acct.sponsorRef || '';
await accounts.setSponsorRef(acct.email, ref);
movedByTx.set(ev.tx, { buyerId: Number(ev.id) || 0, toId: onchain, oldRef: old, at: Date.now() }); // read by telegramOnEvent on the Purchase in the same tx
if (movedByTx.size > 200) { const k = movedByTx.keys().next().value; movedByTx.delete(k); }
console.log('referral moved to where the pay landed:', acct.email, JSON.stringify(old), '->', ref, '(#' + onchain + ')');
// Notices, now that it is a fact on the chain (Marty, 2026-09-16): everyone between the stored
// referrer and the paid sponsor who was simply not activated is told they lost this referral for
@@ -638,6 +640,7 @@ async function sponsorSyncOnEvent(ev) {
if (spAcct && lost.length) sponsorGainNudge(spAcct, nameOf(acct), lost.map(nameOf)).catch(() => {});
} catch (e) { console.error('sponsor move notices', e.message); }
}
const movedByTx = new Map(); // tx -> { buyerId, toId, oldRef } for purchases routed past an unactivated sponsor
async function refAccount(ref) {
const t = String(ref || '').trim().toLowerCase(); if (!t) return null;
let a = await accounts.byCode(t); if (!a) a = await accounts.byUsername(t);
@@ -883,8 +886,41 @@ async function telegramOnEvent(ev) {
if (!line) return null;
return line + ' \u00b7 <a href="' + tx + '">verify</a>' + (sc.telegramCtaUrl ? '\n<a href="' + sc.telegramCtaUrl + '">Join free</a>' : '');
};
if (sc.telegramChatId) { const t = build(String(sc.telegramEvents || 'payouts')); if (t) await telegramSend(sc.telegramChatId, t, sc.telegramTopicId); }
// Missed-payout lines (Marty, 2026-09-16): when a share is passed over, say who missed it and who got it.
// (a) on-chain pass-up: the TierPaid with hops>0 follows the PassedUp events of the same tx, already indexed.
// (b) off-chain walk-up past an unactivated sponsor: recorded by sponsorSyncOnEvent, posted on the Purchase.
let missed = null;
try {
const site = 'https://instantadpay.com/my';
if (ev.type === 'TierPaid' && Number(ev.hops) > 0) {
const all = chain.recentEvents(1e9);
const sk = all.filter(x => x.type === 'PassedUp' && x.tx === ev.tx && x.tier === ev.tier);
if (sk.length) {
const nm2 = await accounts.namesForMembers(sk.map(x => x.skippedId)).catch(() => ({}));
const need = ev.tier === 2 ? 2 : 5;
const parts = sk.map(x => {
const had = all.filter(y => y.type === 'BuyerCounted' && y.sponsorId === x.skippedId && y.block < ev.block).length;
const why = x.reason === 'unqualified' ? 'not qualified for level ' + ev.tier + ' yet: ' + had + ' of ' + need + ' qualifying buyers' : x.reason === 'send-failed' ? 'the wallet refused the payment' : String(x.reason || '');
return '#' + x.skippedId + (nm2[x.skippedId] ? ' @' + nm2[x.skippedId] : '') + ' (' + why + ')';
});
missed = '\u{1F62C} <b>Missed payout</b>: ' + parts.join(' and ') + ' passed over on ' + who(ev.buyerId) + "'s purchase. <b>" + weiToPol(ev.amountWei) + ' POL</b> went to ' + who(ev.recipientId) + ' instead.'
+ '\nQualify before the next one: <a href="' + site + '">' + site.replace(/^https:\/\//, '') + '</a>';
}
}
if (ev.type === 'Purchase' && movedByTx.has(ev.tx)) {
const mv = movedByTx.get(ev.tx); movedByTx.delete(ev.tx);
const lostAcct = await refAccount(mv.oldRef);
if (lostAcct) {
const lostName = lostAcct.username ? '@' + lostAcct.username : lostAcct.email.replace(/@.*/, '') + '@';
const nm3 = await accounts.namesForMembers([mv.toId]).catch(() => ({}));
missed = '\u{1F62C} <b>Missed sale</b>: ' + lostName + ' had not switched on payouts, so ' + who(ev.buyerId) + "'s $" + Math.round(ev.priceCents / 100) + ' purchase routed past them. That buyer, and every payout from them, now belongs to #' + mv.toId + (nm3[mv.toId] ? ' @' + nm3[mv.toId] : '') + ' for good.'
+ '\nTwo minutes on the Wallet tab prevents this: <a href="' + site + '#wallet">' + site.replace(/^https:\/\//, '') + '</a>';
}
}
} catch (e) { console.error('missed-payout post', e.message); }
if (sc.telegramChatId) { const t = build(String(sc.telegramEvents || 'payouts')); if (t) await telegramSend(sc.telegramChatId, t, sc.telegramTopicId); if (missed && String(sc.telegramEvents || 'payouts') !== 'none') await telegramSend(sc.telegramChatId, missed, sc.telegramTopicId).catch(() => {}); }
if (sc.telegramEchoChatId) { const t = build(String(sc.telegramEchoEvents || 'payouts')); if (t) await telegramSend(sc.telegramEchoChatId, '\u{1F7E0} <b>InstantAdPay</b> \u00b7 ' + t, sc.telegramEchoTopicId); }
if (missed && sc.telegramEchoChatId && String(sc.telegramEchoEvents || 'payouts') !== 'none') await telegramSend(sc.telegramEchoChatId, '\u{1F7E0} <b>InstantAdPay</b> \u00b7 ' + missed, sc.telegramEchoTopicId).catch(() => {});
}
// holding-tank arrivals -> one digest line in the shared payments topic (Marty, 2026-09-12): who is
// waiting for a sponsor, by username, so builders go adopt them. Runs every 15 min, posts only