Missed-payout notice shows the whole chain, one message per purchase
Marty (2026-09-19): show every missed qualification so the whole chain is visible. The old notice fired once per passed-up tier and showed only that tier. AdminPaid is the last event of every purchase transaction (119 of 119 in the index), so the notice now fires there, gathers every PassedUp and TierPaid of the tx, and missed.js composes one message: each tier's walk, every person passed over with their buyer count, who finally caught it (or that the company kept it), and the total POL that walked past how many people on that one sale. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// The missed-payout notice, whole chain in one message (Marty, 2026-09-19).
|
||||
//
|
||||
// A purchase pays three tiers up the sponsor line. When a tier's rightful recipient is not
|
||||
// qualified, the contract passes that share up until it finds someone who is, or hands it to
|
||||
// the company. Every hop is an on-chain event: PassedUp (who was skipped, and why) and TierPaid
|
||||
// (who finally got it, and how many hops up). Until now the Telegram notice reported one tier
|
||||
// per message; this composes ONE message per purchase that shows every tier's walk, every
|
||||
// person passed, and what it added up to. The point is that the people passed over see, in
|
||||
// numbers, exactly what qualifying would have paid them.
|
||||
//
|
||||
// Pure: takes the events of one transaction plus a name resolver; returns the text or null.
|
||||
'use strict';
|
||||
|
||||
const SHARE = { 1: 0.5, 2: 0.2, 3: 0.1 }; // tier shares of the purchase, for a tier nobody caught
|
||||
const NEED = { 2: 2, 3: 5 }; // qualifying buyers a tier requires
|
||||
|
||||
function fmtPol(wei) {
|
||||
try { return (Number(BigInt(String(wei)) / 10n ** 14n) / 1e4).toFixed(4); } catch (e) { return '0'; }
|
||||
}
|
||||
|
||||
// txEvents: every indexed event sharing the tx. who(id) -> '#id @name'. had(id, tier) -> qualifying
|
||||
// buyers that member had at the time (a number) or null when unknown.
|
||||
function composeMissed(txEvents, who, had, site) {
|
||||
const buy = txEvents.find(e => e.type === 'Purchase');
|
||||
const skips = txEvents.filter(e => e.type === 'PassedUp');
|
||||
if (!buy || !skips.length) return null;
|
||||
const price = '$' + Math.round((buy.priceCents || 0) / 100);
|
||||
const lines = ['\u{1F62C} <b>Missed payouts</b> on ' + who(buy.buyerId) + "'s " + price + ' purchase:'];
|
||||
const passed = new Set(); let walked = 0;
|
||||
for (const tier of [2, 3]) {
|
||||
const sk = skips.filter(s => Number(s.tier) === tier);
|
||||
if (!sk.length) continue;
|
||||
const paid = txEvents.find(e => e.type === 'TierPaid' && Number(e.tier) === tier);
|
||||
const pol = paid ? Number(fmtPol(paid.amountWei)) : Number(fmtPol(BigInt(String(buy.paidWei || 0)) * BigInt(Math.round(SHARE[tier] * 100)) / 100n));
|
||||
walked += pol;
|
||||
const hops = sk.map(s => {
|
||||
passed.add(s.skippedId);
|
||||
let why;
|
||||
if (s.reason === 'unqualified') { const n = had(s.skippedId, tier); why = (n == null ? 'not qualified' : n + ' of ' + NEED[tier] + ' qualifying buyers'); }
|
||||
else if (s.reason === 'send-failed') why = 'wallet refused the payment';
|
||||
else why = String(s.reason || 'passed');
|
||||
return who(s.skippedId) + ' (' + why + ')';
|
||||
});
|
||||
const end = paid ? 'paid to ' + who(paid.recipientId) : 'nobody above was qualified, so the company kept it';
|
||||
lines.push('Level ' + tier + ' · <b>' + pol.toFixed(4) + ' POL</b>: ' + hops.join(' → ') + ' → ' + end);
|
||||
}
|
||||
const n = passed.size;
|
||||
lines.push('<b>' + walked.toFixed(4) + ' POL walked past ' + n + ' ' + (n === 1 ? 'person' : 'people') + ' on one ' + price + ' sale.</b>');
|
||||
lines.push('Qualify before the next one: <a href="' + site + '">' + site.replace(/^https:\/\//, '') + '</a>');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
module.exports = { composeMissed, fmtPol };
|
||||
@@ -28,7 +28,8 @@ const coach = require('./coach'); // coaching view, nudges, digest, prospects,
|
||||
const tank = require('./tank'); // holding tank: unsponsored free members, adoptions, pay-it-forward
|
||||
const legacy = require('./legacy'); // Faucet Wave / Tier One Ads bridge: welcome-back credits for listed emails
|
||||
const traffic = require('./traffic'); // public page views by referring domain (admin Traffic tab)
|
||||
const promos = require('./promos'); // partner promo codes -> free ad credits (link ?promo=CODE or the dashboard box)
|
||||
const promos = require('./promos');
|
||||
const missedNotice = require('./missed'); // the whole-chain missed-payout notice // partner promo codes -> free ad credits (link ?promo=CODE or the dashboard box)
|
||||
const blog = require('./blog');
|
||||
const adminMember = require('./adminmember');
|
||||
const syndicate = require('./syndicate');
|
||||
@@ -952,19 +953,17 @@ async function telegramOnEvent(ev) {
|
||||
let missed = null;
|
||||
try {
|
||||
const site = 'https://instantadpay.com/my';
|
||||
if (ev.type === 'TierPaid' && Number(ev.hops) > 0) {
|
||||
// (a) on-chain pass-ups: AdminPaid is the last event of every purchase tx, so by then every
|
||||
// PassedUp and TierPaid of the tx is indexed; one message shows the whole chain (missed.js).
|
||||
if (ev.type === 'AdminPaid') {
|
||||
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>';
|
||||
const txEvents = all.filter(x => x.tx === ev.tx);
|
||||
if (txEvents.some(x => x.type === 'PassedUp')) {
|
||||
const ids = new Set(); txEvents.forEach(x => { for (const k of ['buyerId', 'skippedId', 'recipientId']) if (x[k]) ids.add(x[k]); });
|
||||
const nm2 = await accounts.namesForMembers([...ids]).catch(() => ({}));
|
||||
const who2 = id => '#' + id + (nm2[id] ? ' @' + nm2[id] : '');
|
||||
const had = (id, tier) => all.filter(y => y.type === 'BuyerCounted' && y.sponsorId === id && y.block < ev.block).length;
|
||||
missed = missedNotice.composeMissed(txEvents, who2, had, site);
|
||||
}
|
||||
}
|
||||
if (ev.type === 'Purchase' && movedByTx.has(ev.tx)) {
|
||||
|
||||
Reference in New Issue
Block a user