Show which generation caught each pass-up + finish domain go-forward

Feature (Marty): live payment proof + notifications now show the
generation for upgrade pass-ups (matrix hop distance payer→recipient),
removing the "why did they get it" question. chain.genBetween() adds a
`gen` field to payout events + /api/public/payouts; rendered as a gold
"Gen N" badge in the feed, "Gen N pass-up" in toasts, and "Gen N …" in
Telegram team alerts.

Domain: rmcircle.team is now live (added to Coolify alongside
rmcircle.saasy.top, SSL issued, both serve, no redirect). Switched the
auto-tweet CTA to https://rmcircle.team.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-16 05:22:47 -05:00
parent 663948595c
commit d0b1eddda2
5 changed files with 19 additions and 6 deletions
+14 -2
View File
@@ -200,6 +200,18 @@ function passedOver(fromId, toId) {
return []; return [];
} }
// Which generation caught a pass-up: matrix hops from the payer up to the
// recipient (1 = direct parent, 2 = grandparent, …). null if not on the chain.
function genBetween(fromId, toId) {
if (!state || !fromId || !toId) return null;
let cur = state.members[fromId] && state.members[fromId].uplineId;
for (let hops = 1; cur && hops <= 40; hops++) {
if (cur === toId) return hops;
cur = state.members[cur] && state.members[cur].uplineId;
}
return null;
}
async function processRange(fromBlock, toBlock) { async function processRange(fromBlock, toBlock) {
const logs = await rpc('eth_getLogs', [{ const logs = await rpc('eth_getLogs', [{
address: CONTRACT, fromBlock: '0x' + fromBlock.toString(16), toBlock: '0x' + toBlock.toString(16), address: CONTRACT, fromBlock: '0x' + fromBlock.toString(16), toBlock: '0x' + toBlock.toString(16),
@@ -258,7 +270,7 @@ async function processRange(fromBlock, toBlock) {
state.payouts.push(p); state.totals.count++; state.totals.pol = +(state.totals.pol + p.pol).toFixed(6); state.payouts.push(p); state.totals.count++; state.totals.pol = +(state.totals.pol + p.pol).toFixed(6);
const rcpt = state.members[p.toId]; const rcpt = state.members[p.toId];
if (rcpt) rcpt.earnedPol = +((rcpt.earnedPol || 0) + p.pol).toFixed(6); if (rcpt) rcpt.earnedPol = +((rcpt.earnedPol || 0) + p.pol).toFixed(6);
emit({ type: 'payout', kind: p.kind, toId: p.toId, fromId: p.fromId, pol: p.pol, levelName: levelName(p.level), upgrade: p.upgrade, tx, ts }); emit({ type: 'payout', kind: p.kind, toId: p.toId, fromId: p.fromId, pol: p.pol, levelName: levelName(p.level), upgrade: p.upgrade, gen: p.kind === 'upline' ? genBetween(p.fromId, p.toId) : undefined, tx, ts });
} }
} }
} }
@@ -344,7 +356,7 @@ function getPayoutsPublic(offset = 0, limit = 40) {
limit: lim, limit: lim,
hasMore: off + lim < reversed.length, hasMore: off + lim < reversed.length,
payouts: page.map(p => ({ payouts: page.map(p => ({
key: p.key, kind: p.kind, toId: p.toId, fromId: p.fromId, key: p.key, kind: p.kind, toId: p.toId, fromId: p.fromId, gen: p.kind === 'upline' ? genBetween(p.fromId, p.toId) : undefined,
level: p.level, levelName: levelName(p.level), pol: p.pol, tx: p.tx, ts: p.ts, desc: p.desc, level: p.level, levelName: levelName(p.level), pol: p.pol, tx: p.tx, ts: p.ts, desc: p.desc,
toAccount: state.members[p.toId] ? state.members[p.toId].account : undefined, toAccount: state.members[p.toId] ? state.members[p.toId].account : undefined,
upgrade: p.upgrade ? { id: p.upgrade.id, newLevel: p.upgrade.newLevel, levelName: levelName(p.upgrade.newLevel), tier: tierName(p.upgrade.tier) } : undefined, upgrade: p.upgrade ? { id: p.upgrade.id, newLevel: p.upgrade.newLevel, levelName: levelName(p.upgrade.newLevel), tier: tierName(p.upgrade.tier) } : undefined,
+2 -2
View File
@@ -36,7 +36,7 @@
?`🚀 Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)}` ?`🚀 Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)}`
:`💸 Member #${p.toId} just got paid`; :`💸 Member #${p.toId} just got paid`;
const sub=p.kind==='upline' const sub=p.kind==='upline'
?`${fmtPol(p.pol)} POL paid up to Member #${p.toId}` ?`${fmtPol(p.pol)} POL paid up to Member #${p.toId}${p.gen?` · Gen ${p.gen} pass-up`:''}`
:(p.kind==='referral'?`+${fmtPol(p.pol)} POL · direct referral reward`:`+${fmtPol(p.pol)} POL · ${p.desc||p.levelName||'payout'}`); :(p.kind==='referral'?`+${fmtPol(p.pol)} POL · direct referral reward`:`+${fmtPol(p.pol)} POL · ${p.desc||p.levelName||'payout'}`);
el.innerHTML=`<span class="pp-toast-head">${head}</span><span class="pp-toast-sub">${esc(sub)}</span><span class="pp-toast-verify">${timeAgo(p.ts)} · Verify on Polygonscan ↗</span>`; el.innerHTML=`<span class="pp-toast-head">${head}</span><span class="pp-toast-sub">${esc(sub)}</span><span class="pp-toast-verify">${timeAgo(p.ts)} · Verify on Polygonscan ↗</span>`;
getStack().appendChild(el); getStack().appendChild(el);
@@ -50,7 +50,7 @@
if(p.kind==='upline'){ if(p.kind==='upline'){
const up=p.upgrade?`Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)} — `:''; const up=p.upgrade?`Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)} — `:'';
const passed=p.passed&&p.passed.length?`<div class="pp-passed">passed over ${p.passed.map(i=>'#'+i).join(', ')} (not yet eligible — needs 2 directs and the level)</div>`:''; const passed=p.passed&&p.passed.length?`<div class="pp-passed">passed over ${p.passed.map(i=>'#'+i).join(', ')} (not yet eligible — needs 2 directs and the level)</div>`:'';
return `<div class="pp-row"><div class="pp-icon">🚀</div><div class="pp-body"><strong>${up}${fmtPol(p.pol)} POL paid up to Member #${p.toId}</strong><div class="pp-meta">Upgrade pass-up · ${esc(p.levelName)} · ${when}</div>${passed}</div>${verify}</div>`; return `<div class="pp-row"><div class="pp-icon">🚀</div><div class="pp-body"><strong>${up}${fmtPol(p.pol)} POL paid up to Member #${p.toId}</strong><div class="pp-meta">Upgrade pass-up${p.gen?` · <span class="pp-gen">Gen ${p.gen}</span>`:''} · ${esc(p.levelName)} · ${when}</div>${passed}</div>${verify}</div>`;
} }
const meta=p.kind==='referral'?`Direct referral reward from #${p.fromId}'s entry`:`${esc(p.desc||p.levelName)} — from #${p.fromId}`; const meta=p.kind==='referral'?`Direct referral reward from #${p.fromId}'s entry`:`${esc(p.desc||p.levelName)} — from #${p.fromId}`;
return `<div class="pp-row"><div class="pp-icon">💸</div><div class="pp-body"><strong>Member #${p.toId} received ${fmtPol(p.pol)} POL</strong><div class="pp-meta">${meta} · ${when}</div></div>${verify}</div>`; return `<div class="pp-row"><div class="pp-icon">💸</div><div class="pp-body"><strong>Member #${p.toId} received ${fmtPol(p.pol)} POL</strong><div class="pp-meta">${meta} · ${when}</div></div>${verify}</div>`;
+1
View File
@@ -58,6 +58,7 @@ a{color:inherit}.wrap{width:min(1160px,calc(100% - 32px));margin:auto}.nav{heigh
.pp-body{flex:1 1 auto;min-width:0} .pp-body{flex:1 1 auto;min-width:0}
.pp-body strong{font-size:15px} .pp-body strong{font-size:15px}
.pp-meta{color:var(--muted);font-size:12.5px;margin-top:3px} .pp-meta{color:var(--muted);font-size:12.5px;margin-top:3px}
.pp-gen{color:var(--gold);font-weight:800;background:rgba(243,190,67,.12);border:1px solid rgba(243,190,67,.35);border-radius:6px;padding:1px 7px}
.pp-passed{color:#f2c768;font-size:12px;margin-top:3px} .pp-passed{color:#f2c768;font-size:12px;margin-top:3px}
.pp-verify{flex:0 0 auto;font-size:13px;color:var(--teal);text-decoration:none;border:1px solid rgba(78,214,203,.35);border-radius:9px;padding:7px 10px;white-space:nowrap} .pp-verify{flex:0 0 auto;font-size:13px;color:var(--teal);text-decoration:none;border:1px solid rgba(78,214,203,.35);border-radius:9px;padding:7px 10px;white-space:nowrap}
.pp-verify:hover{background:rgba(78,214,203,.1)} .pp-verify:hover{background:rgba(78,214,203,.1)}
+1 -1
View File
@@ -620,7 +620,7 @@ chain.startIndexer(evt=>{
let text; let text;
if(evt.type==='registered')text=`📈 TEAM BUILD: new position!\n#${evt.id} registered under #${evt.referrerId} (${evt.tierName}).`; if(evt.type==='registered')text=`📈 TEAM BUILD: new position!\n#${evt.id} registered under #${evt.referrerId} (${evt.tierName}).`;
else if(evt.type==='upgraded')text=`🚀 TEAM BUILD: #${evt.id} upgraded to ${evt.levelName}.`; else if(evt.type==='upgraded')text=`🚀 TEAM BUILD: #${evt.id} upgraded to ${evt.levelName}.`;
else text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`; else text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (${evt.gen?`Gen ${evt.gen} `:''}upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`;
if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`; if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`;
sendTelegram(text); sendTelegram(text);
// admin email alert — same team-gated events, so deep-leg action still surfaces // admin email alert — same team-gated events, so deep-leg action still surfaces
+1 -1
View File
@@ -14,7 +14,7 @@ const BLOTATO_URL = 'https://backend.blotato.com/v2/posts';
const MIN_GAP_MS = 2600; // ~23/min ceiling, under Blotato's 30/min const MIN_GAP_MS = 2600; // ~23/min ceiling, under Blotato's 30/min
const DATA_DIR = process.env.DATA_DIR || '.'; const DATA_DIR = process.env.DATA_DIR || '.';
const TWEETED_FILE = path.join(DATA_DIR, 'tweeted-payouts.json'); const TWEETED_FILE = path.join(DATA_DIR, 'tweeted-payouts.json');
const DEFAULT_CTA = 'https://rmcircle.saasy.top/?utm_source=x'; // short: Blotato counts raw URL length toward 280 const DEFAULT_CTA = 'https://rmcircle.team/?utm_source=x'; // new primary domain; short (Blotato counts raw URL length toward 280)
const DEFAULT_TAGS = '#CryptoTeamBuild #TheRMCircle #Polygon #Crypto #Web3'; const DEFAULT_TAGS = '#CryptoTeamBuild #TheRMCircle #Polygon #Crypto #Web3';
const DEFAULT_TWITTER_ID = '7998'; // @cryptoteambuild in Blotato const DEFAULT_TWITTER_ID = '7998'; // @cryptoteambuild in Blotato