// Live on-chain payout proof: feed section (if #payoutFeed exists) + timed // toast pop-ups when payments are seen on the RM Circle contract. (function(){ const SCAN='https://polygonscan.com/tx/'; const TOAST_FRESH_S=15*60; // toast anything seen on-chain in the last 15 min const TOAST_SHOW_MS=8000, TOAST_GAP_MS=2500, TOAST_MAX_QUEUE=5; let seen; try{seen=new Set(JSON.parse(sessionStorage.getItem('ctb.seenPayouts')||'[]'))}catch(e){seen=new Set()} function remember(k){seen.add(k);try{sessionStorage.setItem('ctb.seenPayouts',JSON.stringify([...seen].slice(-300)))}catch(e){}} function esc(s){return String(s??'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]))} function fmtPol(n){return n>=100?n.toFixed(1):n.toFixed(2)} function timeAgo(ts){ if(!ts)return ''; const s=Math.max(0,Math.floor(Date.now()/1000-ts)); if(s<60)return 'just now'; if(s<3600)return Math.floor(s/60)+' min ago'; if(s<86400)return Math.floor(s/3600)+'h ago'; return Math.floor(s/86400)+'d ago'; } function verifyHref(p){return p.tx?SCAN+p.tx:'https://polygonscan.com/address/'+(p.toAccount||'0x33BdAEEfd6d17D80aE53816c916dFb26c4fB2DAF')} let stack=null; function getStack(){ if(!stack){stack=document.createElement('div');stack.className='pp-stack';document.body.appendChild(stack)} return stack; } const queue=[];let showing=false; function enqueue(p){if(queue.length>=TOAST_MAX_QUEUE)return;queue.push(p);if(!showing)showNext()} function showNext(){ const p=queue.shift(); if(!p){showing=false;return} showing=true; const el=document.createElement('a'); el.className='pp-toast';el.href=verifyHref(p);el.target='_blank';el.rel='noopener noreferrer'; const head=p.kind==='upline'&&p.upgrade ?`🚀 Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)}` :`💸 Member #${p.toId} just got paid`; const sub=p.kind==='upline' ?`${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'}`); el.innerHTML=`${head}${esc(sub)}${timeAgo(p.ts)} · Verify on Polygonscan ↗`; getStack().appendChild(el); requestAnimationFrame(()=>el.classList.add('show')); setTimeout(()=>{el.classList.remove('show');setTimeout(()=>{el.remove();setTimeout(showNext,TOAST_GAP_MS)},450)},TOAST_SHOW_MS); } function rowHtml(p){ const when=timeAgo(p.ts); const verify=`Verify ↗`; if(p.kind==='upline'){ const up=p.upgrade?`Member #${p.upgrade.id} upgraded to ${esc(p.upgrade.levelName)} — `:''; const passed=p.passed&&p.passed.length?`
passed over ${p.passed.map(i=>'#'+i).join(', ')} (not yet eligible — needs 2 directs and the level)
`:''; return `
🚀
${up}${fmtPol(p.pol)} POL paid up to Member #${p.toId}
Upgrade pass-up${p.gen?` · Gen ${p.gen}`:''} · ${esc(p.levelName)} · ${when}
${passed}
${verify}
`; } const meta=p.kind==='referral'?`Direct referral reward from #${p.fromId}'s entry`:`${esc(p.desc||p.levelName)} — from #${p.fromId}`; return `
💸
Member #${p.toId} received ${fmtPol(p.pol)} POL
${meta} · ${when}
${verify}
`; } // Paginated feed: keep a deduped store of payouts (by key), most-recent first. // The live poll refreshes the top (recent) page; "Show more" pulls older pages. const PAGE=12; const store=new Map(); // key -> payout let total=0, visible=PAGE, fetching=false; const allSorted=()=>[...store.values()].sort((a,b)=>(b.ts||0)-(a.ts||0)); function ingest(d){ if(d&&Array.isArray(d.payouts))for(const p of d.payouts)store.set(p.key,p); if(d&&typeof d.total==='number')total=Math.max(total,d.total); } function updateTotals(d){ const totals=document.getElementById('payoutTotals'); if(totals&&d&&d.totals)totals.innerHTML=`
Members on-chain${d.totals.members}
Payouts recorded${d.totals.payouts}
POL paid to members${Math.round(d.totals.pol).toLocaleString()}
`; } function ensureMoreBtn(){ let btn=document.getElementById('ppMore'); if(!btn){ const feed=document.getElementById('payoutFeed'); if(!feed)return null; btn=document.createElement('button'); btn.id='ppMore';btn.type='button';btn.className='btn btn-secondary pp-more'; btn.addEventListener('click',onMoreClick); feed.parentNode.insertBefore(btn,feed.nextSibling); } return btn; } function renderMoreBtn(loadedLen){ const btn=ensureMoreBtn();if(!btn)return; const cap=Math.max(total,loadedLen); if(cap<=PAGE){btn.style.display='none';return} btn.style.display=''; const showingNow=Math.min(visible,cap); if(showingNowstore.size&&store.sizep.ts&&(now-p.ts){remember(p.key);enqueue(p)}); } poll(); setInterval(poll,45000); })();