c5b5ef7f86
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>
146 lines
8.4 KiB
JavaScript
146 lines
8.4 KiB
JavaScript
/* 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');
|
||
});
|
||
})();
|