629df6336c
/join-now now honors ?ref=<id> (resolves that member's moving-link joinTarget; falls back to the global rotation sponsor). Repointed: /start 'Join With Current Sponsor' -> /join-now; /my pitch buttons -> /join-now?ref=<id>; invite page /join/<id> join button -> /join-now?ref=<id>; bare /join redirect -> /join-now. 'Get Started' learn CTAs still go to /start for onboarding. When the flag is off, /join-now gracefully redirects to /start, so the wiring is safe either way. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
7.8 KiB
JavaScript
120 lines
7.8 KiB
JavaScript
/* 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/<newId>. 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=window.ethereum;
|
||
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<d.length;i+=64) w.push(BigInt('0x'+d.slice(i,i+64)));
|
||
costs={ regPrem:w.slice(8,16) }; return costs;
|
||
}
|
||
function regValue(){ return costs.regPrem[0]*105n/100n; } // premium base * 1.05 (verified)
|
||
|
||
async function readPosition(addr){
|
||
try{ var r=await ethCall(SEL_GETMEMBER+addrWord(addr)); if(!r||r==='0x') return null;
|
||
var d=r.slice(2), level=parseInt(d.slice(5*64,6*64),16); return level?{level:level}:null; }catch(e){ return null; }
|
||
}
|
||
async function ensurePolygon(){
|
||
var cid=await req('eth_chainId'); if(cid===POLYGON_HEX) return true;
|
||
try{ await req('wallet_switchEthereumChain',[{chainId:POLYGON_HEX}]); return true; }
|
||
catch(e){ if(e&&e.code===4902){ await req('wallet_addEthereumChain',[{chainId:POLYGON_HEX,chainName:'Polygon Mainnet',nativeCurrency:{name:'POL',symbol:'POL',decimals:18},rpcUrls:['https://polygon-rpc.com'],blockExplorerUrls:['https://polygonscan.com']}]); return true; } throw e; }
|
||
}
|
||
async function waitReceipt(tx){ for(var i=0;i<60;i++){ try{ var r=await req('eth_getTransactionReceipt',[tx]); if(r) return r; }catch(e){} await new Promise(function(s){setTimeout(s,3000);}); } return null; }
|
||
function receiptEventId(rc){ var id=null; (rc.logs||[]).forEach(function(l){ if(l.address&&l.address.toLowerCase()===CONTRACT&&l.topics&&l.topics[0]===T_REGISTERED) id=parseInt(l.topics[1],16); }); return id; }
|
||
|
||
async function loadSponsor(){
|
||
try{
|
||
// personal invite: ?ref=<id> -> 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 <strong>#'+t.id+'</strong>'+(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 <strong>#'+sp.id+(sp.name?' ('+sp.name+')':'')+'</strong> — the current team placement.'; }
|
||
else $('sponsorLine').textContent='Loading the current team placement…';
|
||
}catch(e){ $('sponsorLine').textContent='Could not load the current sponsor — please refresh.'; }
|
||
}
|
||
|
||
async function refreshBalance(){ try{ var b=await req('eth_getBalance',[account,'latest']); $('bal').textContent=polStr(BigInt(b))+' POL'; }catch(e){} }
|
||
|
||
async function connect(){
|
||
if(!eth){ log('No wallet detected. Open this page in MetaMask or Trust Wallet (extension, or the wallet app’s in-app browser).','err'); return; }
|
||
try{
|
||
log('Requesting wallet connection…');
|
||
var accs=await req('eth_requestAccounts'); account=accs[0];
|
||
await ensurePolygon(); 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 — <a href="/my">open your dashboard</a>. To create a <em>new</em> position, connect a different wallet.'; $('joinBtn').disabled=true; }
|
||
$('joinBox').style.display='block';
|
||
log('Connected. Review the amount, then Join.','ok');
|
||
}catch(e){ log('Connect failed: '+(e.message||e),'err'); }
|
||
}
|
||
|
||
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… <a href="https://polygonscan.com/tx/'+tx+'" target="_blank" rel="noopener">view tx</a>','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){
|
||
$('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 <strong>#'+newId+'</strong>. Redirecting to your dashboard…','ok');
|
||
try{ el_scroll('result'); }catch(e){}
|
||
setTimeout(function(){ location.href='/my/'+newId; }, 4500);
|
||
} else {
|
||
log('Confirmed on-chain. Open your dashboard at <a href="/my">/my</a> 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'}); }
|
||
|
||
document.addEventListener('DOMContentLoaded',function(){
|
||
loadSponsor();
|
||
$('connectBtn').addEventListener('click',connect);
|
||
$('joinBtn').addEventListener('click',doJoin);
|
||
if(eth&ð.on){
|
||
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(); });
|
||
}
|
||
if(!eth) log('No wallet detected. Use MetaMask or Trust Wallet (extension, or the wallet app’s in-app browser).','err');
|
||
});
|
||
})();
|