Wallet discovery: detect Phantom (window.phantom.ethereum) + legacy providers[]

Phantom exposes its EVM provider at window.phantom.ethereum and does not always
fire EIP-6963, so the picker never saw it. probeInjected() now folds in Phantom
and any window.ethereum.providers[] entries (deduped by identity + name) for
both pick() and list(). Phantom added to the mobile in-app-browser hint and the
dashboard no-wallet message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-26 05:14:57 -05:00
parent a3f88c3e33
commit 6cf78a37b7
3 changed files with 30 additions and 5 deletions
+1 -1
View File
@@ -317,7 +317,7 @@
const err=document.getElementById('msgAuthErr');
try{
const eth=await window.RMCWallet.pick();
if(!eth){err.textContent='No wallet found in this browser. On a phone, open your wallet app (SafePal, MetaMask, Trust, Coinbase…), go to its Browser or DApp tab, and type this page\'s address there — your wallet connects automatically.';return;}
if(!eth){err.textContent='No wallet found in this browser. On a phone, open your wallet app (SafePal, Phantom, MetaMask, Trust, Coinbase…), go to its Browser or DApp tab, and type this page\'s address there — your wallet connects automatically.';return;}
const accs=await eth.request({method:'eth_requestAccounts'});const account=accs[0];
const 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.');
+28 -3
View File
@@ -15,6 +15,31 @@
function request(){ try{ window.dispatchEvent(new Event('eip6963:requestProvider')); }catch(e){} }
request();
// Some wallets don't fire EIP-6963 in every setup (notably Phantom, whose EVM
// provider lives at window.phantom.ethereum), and older multi-wallet setups
// expose window.ethereum.providers[]. Fold those in so nobody's wallet is
// invisible at Connect. Dedupe by provider identity AND name.
function probeInjected(list){
function add(provider,name,rdns){
if(!provider) return;
if(list.some(function(w){return w.provider===provider||(w.info&&w.info.name===name);})) return;
list.push({info:{name:name,rdns:rdns||'',uuid:'',icon:''},provider:provider});
}
try{
var eth=window.ethereum;
if(eth&&Array.isArray(eth.providers)){
eth.providers.forEach(function(p){
if(!p)return;
var nm=p.isPhantom?'Phantom':p.isMetaMask?'MetaMask':p.isCoinbaseWallet?'Coinbase Wallet':p.isTrust||p.isTrustWallet?'Trust Wallet':p.isRabby?'Rabby':'Injected wallet';
add(p,nm);
});
}
if(window.phantom&&window.phantom.ethereum) add(window.phantom.ethereum,'Phantom','app.phantom');
if(eth&&eth.isPhantom&&!(window.phantom&&window.phantom.ethereum)) add(eth,'Phantom','app.phantom');
}catch(e){}
return list;
}
function showPicker(list){
return new Promise(function(resolve){
var ov=document.createElement('div');
@@ -45,13 +70,13 @@
return new Promise(function(resolve){
request();
setTimeout(function(){
var list=found.slice();
var list=probeInjected(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);
}, 220);
});
},
list:function(){ return found.slice(); }
list:function(){ return probeInjected(found.slice()); }
};
})();