Files
rm-circle-team-router/public/join-now.js
T
martbost 629df6336c Wire the join funnel to the live /join-now self-enroll page
/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>
2026-08-18 07:37:19 -05:00

120 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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&&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');
});
})();