fetch('/api/public/config').then(function(r){return r.json()}).then(function(c){if(c&&c.polUsd>0)window.__polUsd=c.polUsd;}).catch(function(){}); /* RM Circle — public direct-join engine (dormant fallback; served only when config.dappFallbackPublic is on). Talks only to window.ethereum (CSP-safe). Auto-assigns the current rotation sponsor, registers the member's own wallet, then sends them to their live position page /my/. Zero custody. */ (function(){ 'use strict'; var CONTRACT='0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf'; var POLYGON_HEX='0x89'; var SEL_REGISTER='0x30de37e4'; // register(uint48 sponsorId, uint8 tier) payable var SEL_GETMEMBER='0x2ada2596'; // getMember(address) var SEL_GETCOSTS='0x735f87b9'; // getAllCosts() var T_REGISTERED='0xe4a74887d749eb048f14bfef37b204477f3a5ff67055908b7c8cc62c202aef17'; var $=function(id){return document.getElementById(id);}; var eth=null; // resolved via the wallet picker on connect var account=null, costs=null, sponsorId=null; function log(msg,kind){var b=$('log');if(!b)return;var d=document.createElement('div');d.className='dj-log '+(kind||'');d.innerHTML=msg;b.appendChild(d);b.scrollTop=b.scrollHeight;} function word(n){return BigInt(n).toString(16).padStart(64,'0');} function addrWord(a){return a.toLowerCase().replace(/^0x/,'').padStart(64,'0');} function polStr(wei){return (Number(wei)/1e18).toFixed(2);} function hexWei(wei){return '0x'+BigInt(wei).toString(16);} async function req(method,params){ if(!eth) throw new Error('No wallet found'); return await eth.request({method:method,params:params||[]}); } async function ethCall(data){ return await req('eth_call',[{to:CONTRACT,data:data},'latest']); } async function loadCosts(){ var r=await ethCall(SEL_GETCOSTS); var d=r.slice(2), w=[]; for(var i=0;i -> place in that member's leg via the site's moving-link joinTarget var ref=new URLSearchParams(location.search).get('ref'); if(ref&&/^\d{1,15}$/.test(ref)){ try{ var rm=await (await fetch('/api/public/member?id='+ref)).json(); var t=rm&&rm.joinTarget; if(t&&t.id){ sponsorId=String(t.id); $('sponsorLine').innerHTML='You’ll join under #'+t.id+''+(String(t.id)!==String(ref)?' — the next open spot in #'+ref+'’s team':'')+'.'; return; } }catch(e){} } var j=await (await fetch('/api/public/current-sponsor')).json(); var sp=j&&j.sponsor; if(sp&&sp.id){ sponsorId=String(sp.id); $('sponsorLine').innerHTML='You’ll join the team under #'+sp.id+(sp.name?' ('+sp.name+')':'')+' — the current team placement.'; } else $('sponsorLine').textContent='Loading the current team placement…'; }catch(e){ $('sponsorLine').textContent='Could not load the current sponsor — please refresh.'; } } var lastBal=null; async function refreshBalance(){ try{ var b=await req('eth_getBalance',[account,'latest']); lastBal=BigInt(b); $('bal').textContent=polStr(lastBal)+' POL'; updateFundBox(); }catch(e){} } // Card on-ramp: shown only when the connected wallet can't cover the entry. function updateFundBox(){ var box=$('fundBox'); if(!box||!costs||lastBal===null) return; var need=regValue(); if(lastBal>=need){ box.style.display='none'; return; } var shortPol=Math.max(62,Math.ceil(Number(need-lastBal)/1e18)+5); $('fundMsg').textContent='This wallet holds '+polStr(lastBal)+' POL; the Premium entry is '+polStr(need)+' POL plus a little gas (like a car, every blockchain transaction burns a bit of fuel - keep some in the tank). You can buy the remaining ~'+shortPol+' POL'+(window.__polUsd>0?' (about $'+Math.round(Number(shortPol.replace(/,/g,''))*window.__polUsd)+')':'')+' with a debit/credit card, Apple Pay, or Google Pay — delivered straight to this wallet.'; box.dataset.pol=shortPol; box.style.display='block'; } async function openMoonpay(){ try{ var box=$('fundBox'); var r=await (await fetch('/api/public/moonpay-url?address='+encodeURIComponent(account||'')+'&pol='+encodeURIComponent((box&&box.dataset.pol)||''))).json(); if(r&&r.url){ window.open(r.url,'_blank','noopener'); log(r.signed?'MoonPay opened in a new tab with your wallet address pre-filled — complete the purchase there, then come back and refresh your balance.':'MoonPay opened in a new tab. Choose POL on the Polygon network and paste YOUR wallet address ('+account+') as the destination, then come back and refresh your balance.','ok'); } }catch(e){ log('Could not open MoonPay: '+(e.message||e),'err'); } } async function finishConnect(){ await loadCosts(); $('wallet').textContent=account.slice(0,6)+'…'+account.slice(-4); $('connected').style.display='block'; $('connectBtn').style.display='none'; await refreshBalance(); $('joinCost').textContent=polStr(regValue())+' POL'; var pos=await readPosition(account); if(pos){ $('already').style.display='block'; $('already').innerHTML='This wallet already holds position — open your dashboard. To create a new position, connect a different wallet.'; $('joinBtn').disabled=true; } $('joinBox').style.display='block'; } async function connect(){ try{ eth=await window.RMCWallet.pick(); if(!eth){ showWalletHelp(); log('No wallet detected in this browser. On a phone, use the "Open in your wallet" buttons above — on a computer, install/unlock MetaMask and refresh.','err'); return; } attachWalletEvents(); log('Requesting wallet connection…'); var accs=await req('eth_requestAccounts'); account=accs[0]; await ensurePolygon(); await finishConnect(); log('Connected. Review the amount, then Join.','ok'); }catch(e){ log('Connect failed: '+(e.message||e),'err'); } } // Inside a wallet's in-app browser the wallet is often ALREADY connected — // recognize it silently on load (eth_accounts never prompts) instead of // greeting a connected user like a stranger. Skipped when multiple wallets // are injected (desktop) so no picker UI ever pops uninvited. async function autoDetect(){ try{ var list=window.RMCWallet.list(); var p=list.length===1?list[0].provider:(list.length===0?window.ethereum:null); if(!p) return; eth=p; attachWalletEvents(); var accs=await req('eth_accounts',[]); if(!accs||!accs.length){ log('✅ Wallet detected in this browser — tap "Connect wallet" to continue.','ok'); return; } account=accs[0]; var cid=await req('eth_chainId'); if(cid===POLYGON_HEX){ await finishConnect(); log('✅ Already connected as '+account.slice(0,6)+'…'+account.slice(-4)+'. Review the amount, then Join.','ok'); }else{ $('wallet').textContent=account.slice(0,6)+'…'+account.slice(-4); $('connected').style.display='block'; log('Wallet connected ('+account.slice(0,6)+'…'+account.slice(-4)+') but on another network — tap "Connect wallet" to switch to Polygon and continue.','ok'); } }catch(e){} } async function doJoin(){ try{ if(!sponsorId){ log('Still loading the current sponsor — one moment.','err'); await loadSponsor(); if(!sponsorId) return; } await ensurePolygon(); var val=regValue(); var data=SEL_REGISTER+word(sponsorId)+word(2); log('Joining under #'+sponsorId+', Premium — paying '+polStr(val)+' POL. Confirm in your wallet…'); $('joinBtn').disabled=true; var tx=await req('eth_sendTransaction',[{from:account,to:CONTRACT,value:hexWei(val),data:data}]); log('Submitted — waiting for confirmation… view tx','ok'); var rc=await waitReceipt(tx); if(!rc){ log('Still pending. Once it mines, open your dashboard at /my.',''); $('joinBtn').disabled=false; return; } if(rc.status==='0x0'){ log('⚠️ The transaction reverted — no position created, and your POL was returned (minus gas). Please try again.','err'); $('joinBtn').disabled=false; return; } var newId=receiptEventId(rc); if(newId){ reportJoin(newId); $('result').style.display='block'; $('resultId').textContent='#'+newId; $('resultSub').innerHTML='Welcome to the team! Taking you to your position page…'; $('resultLink').href='/my/'+newId; $('resultLink').style.display='inline-flex'; log('🎉 Confirmed — your position is #'+newId+'. Redirecting to your dashboard…','ok'); try{ el_scroll('result'); }catch(e){} try{sessionStorage.setItem('rmc.fresh','1');}catch(e){} setTimeout(function(){ location.href='/my/'+newId; }, 4500); } else { log('Confirmed on-chain. Open your dashboard at /my and enter your new ID (also shown on Polygonscan).','ok'); $('joinBtn').disabled=false; } }catch(e){ log('Join failed / rejected: '+(e.message||e),'err'); $('joinBtn').disabled=false; } } function el_scroll(id){ var e=$(id); if(e&&e.scrollIntoView) e.scrollIntoView({behavior:'smooth',block:'center'}); } // Auto-confirm the join server-side: records the submission (source + clickid // attribution), fires the purchase event + BeMob postback, enqueues rotation // joins, and alerts the team — same pipeline as the manual submit-ID form. function reportJoin(newId){ try{ var nameEl=$('memberName'); var name=(nameEl&&nameEl.value?nameEl.value.trim().slice(0,60):'')||('Self-enrolled #'+newId); var src=(window.ctbGetSource?window.ctbGetSource():'(direct)'); var cid=(window.ctbGetClickId?window.ctbGetClickId():''); fetch('/api/public/submit-id',{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({newId:String(newId),memberName:name,sponsorId:String(sponsorId||'?'),source:src,clickid:cid}) }).catch(function(){}); }catch(e){} } // Mobile browsers have no injected wallet — offer deep links that reopen // THIS page (query string included, so ?ref= survives) inside the wallet // app's own browser, where window.ethereum exists. function showWalletHelp(){ var box=$('walletHelp'); if(!box) return; var here=location.host+location.pathname+location.search; var mm=$('mmDeep'); if(mm) mm.href='https://metamask.app.link/dapp/'+here; var tw=$('twDeep'); if(tw) tw.href='https://link.trustwallet.com/open_url?coin_id=966&url='+encodeURIComponent(location.href); // Fill the "type this in your wallet's browser" hint with the sponsor's // real join path so SafePal/Coinbase/etc. users land on the right page. var wb=$('wbId'); if(wb){ var ref=new URLSearchParams(location.search).get('ref'); wb.parentNode.textContent='rmcircle.team'+(ref&&/^\d{1,15}$/.test(ref)?'/join/'+ref:'/join-now'); } box.style.display='block'; } function attachWalletEvents(){ if(!eth||!eth.on||eth.__rmcBound) return; eth.__rmcBound=true; eth.on('accountsChanged',function(accs){ account=(accs&&accs[0])||null; if(!account){ $('connected').style.display='none'; $('connectBtn').style.display=''; } else { $('wallet').textContent=account.slice(0,6)+'…'+account.slice(-4); refreshBalance(); } }); eth.on('chainChanged',function(){ if(account) refreshBalance(); }); } document.addEventListener('DOMContentLoaded',function(){ loadSponsor(); // Telegram Mini App webview: no wallet can ever be injected here, so lead // with the deep links immediately — the join finishes in the wallet app's // own browser (same page, sponsor + attribution carried in the URL). if(window.__rmcInTg){ showWalletHelp(); var hd=$('walletHelp')&&$('walletHelp').firstElementChild; if(hd)hd.textContent='📱 One tap to finish in your wallet app.'; log('You’re in Telegram — joining happens in your wallet app’s secure browser. Tap your wallet above; this page reopens there with your sponsor already set.','ok'); } $('connectBtn').addEventListener('click',connect); $('joinBtn').addEventListener('click',doJoin); var fb=$('fundBtn'); if(fb)fb.addEventListener('click',openMoonpay); var fr=$('fundRefresh'); if(fr)fr.addEventListener('click',function(){ log('Checking your balance…'); refreshBalance(); }); setTimeout(autoDetect,350); // let EIP-6963 announcements land first }); })();