Files
rm-circle-team-router/public/direct-join.js
T
martbost c5b5ef7f86 Fix infinite refresh loop on /direct-join with Trust Wallet/Brave
chainChanged handler called location.reload(); wallets that fire chainChanged
on page load turned that into an endless refresh that blocked Connect. Handle
accountsChanged/chainChanged in place instead of reloading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 06:12:32 -05:00

146 lines
8.4 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 — direct on-chain enrollment (contingency tool).
Talks ONLY to the injected wallet provider (window.ethereum), so it needs no
external libraries and no network of its own — CSP-safe (connect-src 'self').
It builds transactions to the RM Circle contract that the USER signs and pays;
this page has zero custody and zero authority. If msg.value is ever wrong the
contract simply reverts and the user keeps their POL (minus trivial gas). */
(function(){
'use strict';
var CONTRACT='0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf';
var POLYGON_HEX='0x89'; // 137
// function selectors (decoded from live transactions)
var SEL_REGISTER='0x30de37e4'; // register(uint48 sponsorId, uint8 tier) payable
var SEL_UPGRADE ='0xd55ec697'; // upgrade() payable
var SEL_GETMEMBER='0x2ada2596';// getMember(address) view
var SEL_GETCOSTS='0x735f87b9'; // getAllCosts() view -> 4x uint256[8] (reg std, reg prem, up std, up prem)
var LEVELS=['','Scintilla','Ascensus','Fabrica','Culmen','Apex','Fastigium','Vertex','Corona'];
var $=function(id){return document.getElementById(id);};
var eth=window.ethereum;
var account=null, costs=null;
function log(msg,kind){var b=$('log');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');} // uint -> 32-byte hex word
function addrWord(a){return a.toLowerCase().replace(/^0x/,'').padStart(64,'0');}
function polStr(wei){return (Number(wei)/1e18).toFixed(4);}
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']); }
// ---- reads (via the wallet's own RPC) ----
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)));
// 4 fixed arrays of 8: [0-7] reg std, [8-15] reg prem, [16-23] up std, [24-31] up prem
costs={ regStd:w.slice(0,8), regPrem:w.slice(8,16), upStd:w.slice(16,24), upPrem:w.slice(24,32) };
return costs;
}
// registration price = base * 105/100 (5% on top — verified against live joins)
function regValue(tier){ var base=(tier===2?costs.regPrem:costs.regStd)[0]; return base*105n/100n; }
// upgrade price = the exact next-level cost (no add-on — verified against live upgrades)
function upValue(tier,level){ var arr=(tier===2?costs.upPrem:costs.upStd); return arr[level-1]; }
async function readPosition(addr){
try{
var r=await ethCall(SEL_GETMEMBER+addrWord(addr)); if(!r||r==='0x') return null;
var d=r.slice(2), tier=parseInt(d.slice(4*64,5*64),16), level=parseInt(d.slice(5*64,6*64),16);
var directs=parseInt(d.slice(6*64,7*64),16);
if(!level) return null; // unregistered
return {tier:tier,level:level,directs:directs};
}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){ // chain not added
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 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 a browser with MetaMask (extension or the MetaMask 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();
// premium join cost preview
$('joinCost').textContent=polStr(regValue(2))+' POL';
var pos=await readPosition(account);
if(pos){
$('posBox').style.display='block';
$('posInfo').innerHTML='This wallet already holds a position: <strong>'+LEVELS[pos.level]+'</strong> (level '+pos.level+'/8, '+(pos.tier===2?'Premium':'Standard')+' tier, '+pos.directs+'/2 directs).';
if(pos.level<8){ $('upBox').style.display='block'; $('upNext').textContent=LEVELS[pos.level+1]; $('upCost').textContent=polStr(upValue(pos.tier,pos.level))+' POL'; $('upgradeBtn').dataset.tier=pos.tier; $('upgradeBtn').dataset.level=pos.level; }
else $('upInfo').innerHTML='Already at the top level (Corona).';
log('Connected. This wallet is registered — Upgrade is available; Join a new position would need a fresh wallet.','ok');
} else {
log('Connected. This wallet has no position yet — you can Register a new position below.','ok');
}
}catch(e){ log('Connect failed: '+(e.message||e),'err'); }
}
async function doRegister(){
try{
var sponsor=($('sponsorId').value||'').trim();
if(!/^\d{1,15}$/.test(sponsor)){ log('Enter a valid numeric sponsor ID first.','err'); return; }
var tier=$('tier').value==='1'?1:2;
await ensurePolygon();
var val=regValue(tier);
var override=($('customPol').value||'').trim();
if(override){ if(!/^\d+(\.\d+)?$/.test(override)){log('Custom amount must be a number.','err');return;} val=BigInt(Math.round(parseFloat(override)*1e6))*(10n**12n); }
var data=SEL_REGISTER+word(sponsor)+word(tier);
log('Sending REGISTER — sponsor #'+sponsor+', '+(tier===2?'Premium':'Standard')+', paying '+polStr(val)+' POL. Confirm in your wallet…');
var tx=await req('eth_sendTransaction',[{from:account,to:CONTRACT,value:hexWei(val),data:data}]);
log('✅ Submitted: <a href="https://polygonscan.com/tx/'+tx+'" target="_blank" rel="noopener">'+tx.slice(0,18)+'…</a> — wait for it to confirm, then your position is live.','ok');
setTimeout(refreshBalance,4000);
}catch(e){ log('Register failed / rejected: '+(e.message||e),'err'); }
}
async function doUpgrade(){
try{
await ensurePolygon();
var tier=parseInt($('upgradeBtn').dataset.tier,10), level=parseInt($('upgradeBtn').dataset.level,10);
var val=upValue(tier,level);
var override=($('customPol').value||'').trim();
if(override){ if(!/^\d+(\.\d+)?$/.test(override)){log('Custom amount must be a number.','err');return;} val=BigInt(Math.round(parseFloat(override)*1e6))*(10n**12n); }
var data=SEL_UPGRADE;
log('Sending UPGRADE '+LEVELS[level]+' → '+LEVELS[level+1]+', paying '+polStr(val)+' POL. Confirm in your wallet…');
var tx=await req('eth_sendTransaction',[{from:account,to:CONTRACT,value:hexWei(val),data:data}]);
log('✅ Submitted: <a href="https://polygonscan.com/tx/'+tx+'" target="_blank" rel="noopener">'+tx.slice(0,18)+'…</a>','ok');
setTimeout(refreshBalance,4000);
}catch(e){ log('Upgrade failed / rejected: '+(e.message||e),'err'); }
}
document.addEventListener('DOMContentLoaded',function(){
$('connectBtn').addEventListener('click',connect);
$('registerBtn').addEventListener('click',doRegister);
var ub=$('upgradeBtn'); if(ub) ub.addEventListener('click',doUpgrade);
// Update in place on wallet events — do NOT location.reload() here: some wallets
// (Trust Wallet in Brave) fire chainChanged on load, and reloading re-arms the
// handler → infinite refresh loop that blocks the Connect button.
if(eth && eth.on){
eth.on('accountsChanged',function(accs){
account=(accs&&accs[0])||null;
if(!account){ $('connected').style.display='none'; $('connectBtn').style.display=''; log('Wallet disconnected.','err'); }
else { $('wallet').textContent=account.slice(0,6)+'…'+account.slice(-4); refreshBalance(); }
});
eth.on('chainChanged',function(){ if(account) refreshBalance(); });
}
if(!eth) log('No wallet detected in this browser. Use MetaMask or Trust Wallet (extension, or the wallet app’s in-app browser).','err');
});
})();