Add EIP-6963 multi-wallet picker to the join tools

Both /join-now and /direct-join now discover every injected wallet (Brave,
MetaMask, SafePal, Trust, Coinbase…) via EIP-6963 and, when more than one is
present, show a 'Choose your wallet' picker on connect — instead of blindly
grabbing window.ethereum (which silently hangs when e.g. Brave's built-in wallet
wins the slot). Shared public/rmc-wallet.js; provider resolved on connect and
wallet events bound once after. Falls back to window.ethereum for legacy
single-provider wallets. CSP-safe (6963 icons are data URIs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-18 08:01:07 -05:00
parent 853e4736c9
commit 5aceb84863
5 changed files with 84 additions and 23 deletions
+1 -1
View File
@@ -78,4 +78,4 @@
<p class="micro" style="text-align:center">No income is guaranteed. Cryptocurrency and smart-contract participation carry risk. Only use funds you can afford to lose.</p>
</main>
<script src="/direct-join.js"></script></body></html>
<script src="/rmc-wallet.js"></script><script src="/direct-join.js"></script></body></html>
+1 -1
View File
@@ -49,4 +49,4 @@
<p class="micro" style="text-align:center">No income is guaranteed. Cryptocurrency and smart-contract participation carry risk. Only use funds you can afford to lose.</p>
</main>
<footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon. Not an earnings guarantee or investment advice. Participation carries risk of loss.<div class="footer-links"><a href="/">Home</a><a href="/training">Training</a><a href="/how-pay-works">How You Get Paid</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a></div></footer>
<script src="/join-now.js"></script></body></html>
<script src="/rmc-wallet.js"></script><script src="/join-now.js"></script></body></html>
+16 -14
View File
@@ -18,7 +18,7 @@
var LEVELS=['','Scintilla','Ascensus','Fabrica','Culmen','Apex','Fastigium','Vertex','Corona'];
var $=function(id){return document.getElementById(id);};
var eth=window.ethereum;
var eth=null; // resolved via the wallet picker on connect
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;}
@@ -81,8 +81,10 @@
function showResult(big,sub,linkId){ var el=$('result'); if(!el) return; $('resultId').textContent=big; $('resultSub').textContent=sub; var lk=$('resultLink'); if(lk){ if(linkId){ lk.href='/my/'+linkId; lk.style.display='inline-flex'; } else lk.style.display='none'; } el.style.display='block'; try{el.scrollIntoView({behavior:'smooth',block:'center'});}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{
eth=await window.RMCWallet.pick();
if(!eth){ log('No wallet selected or detected. Install/unlock MetaMask, Trust, or SafePal and try again.','err'); return; }
attachWalletEvents();
log('Requesting wallet connection…');
var accs=await req('eth_requestAccounts'); account=accs[0];
await ensurePolygon();
@@ -152,17 +154,17 @@
$('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');
});
// 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. Bound once, after the picker resolves the provider.
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=''; log('Wallet disconnected.','err'); }
else { $('wallet').textContent=account.slice(0,6)+'…'+account.slice(-4); refreshBalance(); }
});
eth.on('chainChanged',function(){ if(account) refreshBalance(); });
}
})();
+9 -7
View File
@@ -12,7 +12,7 @@
var T_REGISTERED='0xe4a74887d749eb048f14bfef37b204477f3a5ff67055908b7c8cc62c202aef17';
var $=function(id){return document.getElementById(id);};
var eth=window.ethereum;
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;}
@@ -60,8 +60,10 @@
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{
eth=await window.RMCWallet.pick();
if(!eth){ log('No wallet selected or detected. Install/unlock MetaMask, Trust, or SafePal and try again.','err'); return; }
attachWalletEvents();
log('Requesting wallet connection…');
var accs=await req('eth_requestAccounts'); account=accs[0];
await ensurePolygon(); await loadCosts();
@@ -106,14 +108,14 @@
}
function el_scroll(id){ var e=$(id); if(e&&e.scrollIntoView) e.scrollIntoView({behavior:'smooth',block:'center'}); }
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();
$('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');
});
})();
+57
View File
@@ -0,0 +1,57 @@
/* RM Circle — EIP-6963 multi-wallet discovery + picker.
Lets the user choose among ALL injected wallets (Brave, MetaMask, SafePal,
Trust, Coinbase…) instead of whatever won the window.ethereum fight — which is
why "connect" silently hangs when e.g. Brave's built-in wallet grabs the slot.
CSP-safe: no external libs, and EIP-6963 wallet icons are data: URIs. */
(function(){
'use strict';
var found=[], seen={};
function onAnnounce(e){
var d=e&&e.detail; if(!d||!d.info||!d.provider) return;
var key=d.info.uuid||d.info.rdns||d.info.name; if(seen[key]) return; seen[key]=1;
found.push({info:d.info, provider:d.provider});
}
window.addEventListener('eip6963:announceProvider', onAnnounce);
function request(){ try{ window.dispatchEvent(new Event('eip6963:requestProvider')); }catch(e){} }
request();
function showPicker(list){
return new Promise(function(resolve){
var ov=document.createElement('div');
ov.style.cssText='position:fixed;inset:0;background:rgba(3,10,18,.74);display:flex;align-items:center;justify-content:center;z-index:99999;padding:20px';
var box=document.createElement('div');
box.style.cssText='background:#0b1e30;border:1px solid #27445e;border-radius:16px;padding:20px;max-width:360px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,.55)';
var h=document.createElement('div'); h.style.cssText='font-weight:800;font-size:17px;color:#f7f9fc;margin-bottom:4px'; h.textContent='Choose your wallet'; box.appendChild(h);
var s=document.createElement('div'); s.style.cssText='color:#aebdca;font-size:13px;margin-bottom:14px'; s.textContent='Pick the wallet you want to connect with.'; box.appendChild(s);
list.forEach(function(w){
var b=document.createElement('button');
b.style.cssText='display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:#132c43;border:1px solid #294a65;border-radius:11px;padding:12px 14px;margin-bottom:8px;cursor:pointer;color:#f7f9fc;font-weight:700;font-size:15px';
if(w.info.icon){ var img=document.createElement('img'); img.src=w.info.icon; img.alt=''; img.style.cssText='width:26px;height:26px;border-radius:6px;flex:none'; b.appendChild(img); }
var sp=document.createElement('span'); sp.textContent=w.info.name||'Wallet'; b.appendChild(sp);
b.addEventListener('click',function(){ try{document.body.removeChild(ov);}catch(e){} resolve(w.provider); });
box.appendChild(b);
});
var c=document.createElement('button'); c.textContent='Cancel';
c.style.cssText='width:100%;background:transparent;border:0;color:#8498aa;padding:8px;margin-top:2px;cursor:pointer;font-size:13px';
c.addEventListener('click',function(){ try{document.body.removeChild(ov);}catch(e){} resolve(null); });
box.appendChild(c);
ov.appendChild(box); document.body.appendChild(ov);
});
}
window.RMCWallet={
// Resolve a provider: 0 discovered -> legacy window.ethereum; 1 -> use it; 2+ -> ask.
pick:function(){
return new Promise(function(resolve){
request();
setTimeout(function(){
var list=found.slice();
if(list.length===0){ resolve(window.ethereum||null); return; }
if(list.length===1){ resolve(list[0].provider); return; }
showPicker(list).then(resolve);
}, 160);
});
},
list:function(){ return found.slice(); }
};
})();