fix: comprehensive wallet detection - generic window.solana fallback, multi-provider array, wallet standard, 150ms async delay, dedup

This commit is contained in:
jae 2026-04-05 19:25:13 +00:00
parent f1a4437bf9
commit 35286a0a39

View file

@ -230,97 +230,117 @@ async function doReverse() {
// ─── WALLET ───────────────────────────────────────────────── // ─── WALLET ─────────────────────────────────────────────────
let connectedProvider = null; let connectedProvider = null;
const WALLETS = [
{
name: 'PHANTOM',
icon: '👻',
detect: () => {
const p = window.phantom?.solana;
return (p && p.isPhantom) ? p : null;
},
url: 'https://phantom.app'
},
{
name: 'JUPITER',
icon: '🪐',
detect: () => {
if (window.jupiter?.solana) return window.jupiter.solana;
if (window.solana?.isJupiter) return window.solana;
return null;
},
url: 'https://jup.ag'
},
{
name: 'SOLFLARE',
icon: '🔆',
detect: () => {
const s = window.solflare;
return (s && s.isSolflare) ? s : null;
},
url: 'https://solflare.com'
},
{
name: 'BACKPACK',
icon: '🎒',
detect: () => {
const b = window.backpack;
return (b && b.isBackpack) ? b : null;
},
url: 'https://backpack.app'
},
{
name: 'COINBASE',
icon: '🔵',
detect: () => window.coinbaseSolana || null,
url: 'https://coinbase.com/wallet'
},
{
name: 'TRUST',
icon: '🛡️',
detect: () => window.trustwallet?.solana || null,
url: 'https://trustwallet.com'
},
{
name: 'METAMASK',
icon: '🦊',
detect: () => {
// MetaMask Solana injects at window.solana with isMetaMask
if (window.solana?.isMetaMask) return window.solana;
// Or check ethereum provider flags
if (window.ethereum?.isMetaMask && window.ethereum?.solana) return window.ethereum.solana;
return null;
},
url: 'https://metamask.io'
},
];
function getAvailableWallets() { function getAvailableWallets() {
const available = []; const found = [];
const matchedProviders = new Set(); const seen = new Set();
for (const w of WALLETS) { function add(name, icon, provider, url) {
try { if (!provider || seen.has(provider)) return;
const provider = w.detect(); seen.add(provider);
if (provider && !matchedProviders.has(provider)) { found.push({ name, icon, provider, url });
available.push({ ...w, provider }); }
matchedProviders.add(provider);
// ── Specific well-known injection points ──
// Phantom
try {
const p = window.phantom?.solana;
if (p && p.isPhantom) add('PHANTOM', '👻', p, 'https://phantom.app');
} catch(e) {}
// Solflare
try {
const s = window.solflare;
if (s && s.isSolflare) add('SOLFLARE', '🔆', s, 'https://solflare.com');
} catch(e) {}
// Backpack
try {
const b = window.backpack;
if (b && b.isBackpack) add('BACKPACK', '🎒', b, 'https://backpack.app');
} catch(e) {}
// Coinbase
try {
if (window.coinbaseSolana) add('COINBASE', '🔵', window.coinbaseSolana, 'https://coinbase.com/wallet');
} catch(e) {}
// Trust
try {
const t = window.trustwallet?.solana;
if (t) add('TRUST', '🛡️', t, 'https://trustwallet.com');
} catch(e) {}
// ── Generic window.solana (Jupiter, MetaMask Snap, and others inject here) ──
try {
const ws = window.solana;
if (ws && !seen.has(ws)) {
// Try to identify what it is
if (ws.isJupiter) add('JUPITER', '🪐', ws, 'https://jup.ag');
else if (ws.isMetaMask) add('METAMASK', '🦊', ws, 'https://metamask.io');
else if (ws.isPhantom) { /* already caught above */ }
else if (ws.isSolflare) { /* already caught above */ }
else if (ws.isBackpack) { /* already caught above */ }
else add('SOLANA WALLET', '◈', ws, '#');
}
} catch(e) {}
// ── Jupiter specific paths ──
try {
const j = window.jupiter?.solana;
if (j && !seen.has(j)) add('JUPITER', '🪐', j, 'https://jup.ag');
} catch(e) {}
// ── MetaMask ethereum.solana path ──
try {
if (window.ethereum?.isMetaMask && window.ethereum?.solana && !seen.has(window.ethereum.solana)) {
add('METAMASK', '🦊', window.ethereum.solana, 'https://metamask.io');
}
} catch(e) {}
// ── Multi-provider array (some wallets use this) ──
try {
const providers = window.solana?.providers;
if (Array.isArray(providers)) {
for (const p of providers) {
if (seen.has(p)) continue;
if (p.isPhantom) add('PHANTOM', '👻', p, 'https://phantom.app');
else if (p.isJupiter) add('JUPITER', '🪐', p, 'https://jup.ag');
else if (p.isSolflare) add('SOLFLARE', '🔆', p, 'https://solflare.com');
else if (p.isBackpack) add('BACKPACK', '🎒', p, 'https://backpack.app');
else if (p.isMetaMask) add('METAMASK', '🦊', p, 'https://metamask.io');
else add('WALLET', '◈', p, '#');
} }
} catch(e) {} }
} } catch(e) {}
// Generic fallback: window.solana if nothing matched // ── Wallet Standard discovery (modern wallets) ──
if (available.length === 0 && window.solana) { try {
available.push({ const walletStandard = window.navigator?.wallets;
name: 'SOLANA WALLET', if (walletStandard && typeof walletStandard[Symbol.iterator] === 'function') {
icon: '◈', for (const w of walletStandard) {
provider: window.solana, if (w.features?.['standard:connect'] && !seen.has(w)) {
url: '#' const name = (w.name || 'WALLET').toUpperCase();
}); add(name, w.icon || '◈', w, w.url || '#');
} }
}
}
} catch(e) {}
return available; return found;
} }
// All known wallets for install links
const KNOWN_WALLETS = [
{ name: 'PHANTOM', icon: '👻', url: 'https://phantom.app' },
{ name: 'JUPITER', icon: '🪐', url: 'https://jup.ag' },
{ name: 'SOLFLARE', icon: '🔆', url: 'https://solflare.com' },
{ name: 'BACKPACK', icon: '🎒', url: 'https://backpack.app' },
{ name: 'COINBASE', icon: '🔵', url: 'https://coinbase.com/wallet' },
{ name: 'TRUST', icon: '🛡️', url: 'https://trustwallet.com' },
{ name: 'METAMASK', icon: '🦊', url: 'https://metamask.io' },
];
function initWallet() { function initWallet() {
const btn = $('#wallet-btn'); const btn = $('#wallet-btn');
if (!btn) return; if (!btn) return;
@ -353,54 +373,59 @@ function createWalletModal() {
function openModal() { function openModal() {
const modal = $('#wallet-modal'); const modal = $('#wallet-modal');
const list = $('#wallet-list'); const list = $('#wallet-list');
const available = getAvailableWallets();
const detectedNames = new Set(available.map(w => w.name));
let html = ''; // Small delay to let async provider injection complete
setTimeout(() => {
const available = getAvailableWallets();
const detectedNames = new Set(available.map(w => w.name));
// Show detected wallets as clickable buttons let html = '';
if (available.length > 0) {
html += '<div class="sol-wallet-divider">DETECTED</div>';
html += available.map(w => `
<button class="sol-wallet-option detected" data-wallet="${w.name}">
<span class="sol-wallet-option-icon">${w.icon}</span>
<span class="sol-wallet-option-name">${w.name}</span>
<span class="sol-wallet-option-status detected">DETECTED</span>
</button>
`).join('');
}
// Show not-installed wallets as links (exclude detected ones) if (available.length > 0) {
const notInstalled = WALLETS.filter(w => !detectedNames.has(w.name)); html += '<div class="sol-wallet-divider">DETECTED</div>';
if (notInstalled.length > 0) { html += available.map(w => `
html += '<div class="sol-wallet-divider">NOT INSTALLED</div>'; <button class="sol-wallet-option detected" data-wallet="${w.name}">
html += notInstalled.map(w => ` <span class="sol-wallet-option-icon">${w.icon}</span>
<a href="${w.url}" target="_blank" rel="noopener" class="sol-wallet-option not-installed"> <span class="sol-wallet-option-name">${w.name}</span>
<span class="sol-wallet-option-icon">${w.icon}</span> <span class="sol-wallet-option-status detected">DETECTED</span>
<span class="sol-wallet-option-name">${w.name}</span> </button>
<span class="sol-wallet-option-status">INSTALL </span> `).join('');
</a> }
`).join('');
}
if (available.length === 0) { const notInstalled = KNOWN_WALLETS.filter(w => !detectedNames.has(w.name));
html = '<div class="sol-empty" style="padding:1rem;">NO SOLANA WALLETS DETECTED<br><br>Install a Solana wallet extension to connect.</div>'; if (notInstalled.length > 0) {
} html += '<div class="sol-wallet-divider">NOT INSTALLED</div>';
html += notInstalled.map(w => `
<a href="${w.url}" target="_blank" rel="noopener" class="sol-wallet-option not-installed">
<span class="sol-wallet-option-icon">${w.icon}</span>
<span class="sol-wallet-option-name">${w.name}</span>
<span class="sol-wallet-option-status">INSTALL </span>
</a>
`).join('');
}
list.innerHTML = html; if (available.length === 0) {
html = `<div class="sol-empty" style="padding:1rem;">
NO SOLANA WALLETS DETECTED<br><br>
Install a Solana wallet extension to connect.<br>
<span style="color:#14F195;font-size:0.6rem;">If you just installed one, refresh the page.</span>
</div>`;
}
// Bind click handlers only for detected wallets (buttons, not links) list.innerHTML = html;
list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => {
btn.addEventListener('click', (e) => { list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => {
e.preventDefault(); btn.addEventListener('click', (e) => {
e.stopPropagation(); e.preventDefault();
const name = btn.dataset.wallet; e.stopPropagation();
const wallet = available.find(w => w.name === name); const name = btn.dataset.wallet;
if (wallet) connectWallet(wallet); const wallet = available.find(w => w.name === name);
if (wallet) connectWallet(wallet);
});
}); });
});
modal.classList.remove('hidden'); modal.classList.remove('hidden');
}, 150);
} }
function closeModal() { function closeModal() {