From 5978ef139f8953353e460cce762356cf624ec6e0 Mon Sep 17 00:00:00 2001 From: martbost Date: Mon, 31 Aug 2026 15:21:44 -0500 Subject: [PATCH] Suite: force the wallet account picker when switching position Clearing our session was only half the problem. eth_requestAccounts returns whichever account the wallet already has connected to the site - it never opens the picker - so a member holding two positions could sign out, switch accounts in MetaMask, reconnect, and be handed the same address again. Signing with it resolved to the same position, which looked exactly like the switch doing nothing. This is the trap already documented in our own Triple Play guidance, and the Triple Play is what puts members in this situation in the first place. Switching now calls wallet_requestPermissions first, which forces the account selector open. Only on switch, so ordinary single-position sign-ins are unchanged. And if the wallet still returns the same address, the page now says so - naming the address and pointing at the wallet's connected-sites list - instead of silently redisplaying the same position. Co-Authored-By: Claude Fable 5 --- public/suite.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/public/suite.js b/public/suite.js index 31d9f91..f9311b7 100644 --- a/public/suite.js +++ b/public/suite.js @@ -88,6 +88,8 @@ ev.preventDefault(); sw.textContent = 'signing out…'; try { await fetch('/api/public/signout', { method: 'POST' }); } catch (e) {} + // Tell connect() to force the wallet's account picker on the way back in. + try { sessionStorage.setItem('rmc.switch', '1'); } catch (e) {} location.reload(); }); me = null; pips(); render(); @@ -114,15 +116,40 @@ try { var eth = await window.RMCWallet.pick(); if (!eth) { {var _ia=(window.RMCInApp&&window.RMCInApp.notice('/suite'))||'';if(_ia){err.innerHTML=_ia;if(err.style)err.style.display='block';window.RMCInApp.bind('/suite');}else{err.textContent = 'This browser has no wallet in it. Open this page INSIDE your wallet app instead: open Trust, MetaMask, SafePal or Coinbase, find its Browser (or DApps) tab, and type rmcircle.team/suite into that address bar. On iPhone, Trust no longer has a browser — use MetaMask or SafePal, or ask your sponsor to link you instead.';}} err.style.display = 'block'; btn.disabled = false; btn.textContent = '🔑 Connect wallet — light up my tools'; return; } + // eth_requestAccounts SILENTLY returns whichever account the site is + // already connected to - it never opens the picker. So someone holding + // several positions can switch accounts in MetaMask all day and still be + // handed the first one they ever connected, sign with it, and resolve to + // the same position. Asking for permissions first forces the picker open. + var wantSwitch = false; + try { wantSwitch = sessionStorage.getItem('rmc.switch') === '1'; } catch (e) {} + if (wantSwitch) { + try { await eth.request({ method: 'wallet_requestPermissions', params: [{ eth_accounts: {} }] }); } + catch (pe) { /* wallet may not support it; fall through to the normal request */ } + try { sessionStorage.removeItem('rmc.switch'); } catch (e) {} + } var accs = await eth.request({ method: 'eth_requestAccounts' }); var account = accs[0]; + var shortAcc = account ? account.slice(0, 6) + '…' + account.slice(-4) : ''; try { await window.RMCWallet.ensureChain(eth); } catch (ce) {} var ch = await (await fetch('/api/public/msg-challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account }) })).json(); if (!ch.message) throw new Error(ch.error || 'Could not start sign-in.'); var sig = await eth.request({ method: 'personal_sign', params: [ch.message, account] }); var vr = await (await fetch('/api/public/msg-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account, signature: sig }) })).json(); if (!vr.ok) throw new Error(vr.error || 'Signature check failed.'); + var prevId = null; + try { prevId = sessionStorage.getItem('rmc.lastId'); } catch (e) {} await refreshMe(); + try { + var nowId = (me && me.id) ? String(me.id) : null; // `me` is set by refreshMe() in this same closure + if (nowId) sessionStorage.setItem('rmc.lastId', nowId); + if (prevId && nowId && prevId === nowId) { + err.innerHTML = 'Your wallet handed back the same address (' + shortAcc + '), so this is still position #' + nowId + + '. Wallets keep a site connected to one account: open your wallet, find its connected-sites or permissions list, ' + + 'disconnect rmcircle.team, then tap switch here again and pick the other account.'; + err.style.display = 'block'; + } + } catch (e) {} } catch (e) { var m = String((e && e.message) || e); err.textContent = /not registered|no position/i.test(m)