From 35286a0a398950da7b308875fd443ce1d49a1177 Mon Sep 17 00:00:00 2001 From: jae Date: Sun, 5 Apr 2026 19:25:13 +0000 Subject: [PATCH] fix: comprehensive wallet detection - generic window.solana fallback, multi-provider array, wallet standard, 150ms async delay, dedup --- js/soldomains.js | 273 ++++++++++++++++++++++++++--------------------- 1 file changed, 149 insertions(+), 124 deletions(-) diff --git a/js/soldomains.js b/js/soldomains.js index d945002..5ad2f63 100644 --- a/js/soldomains.js +++ b/js/soldomains.js @@ -230,97 +230,117 @@ async function doReverse() { // ─── WALLET ───────────────────────────────────────────────── 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() { - const available = []; - const matchedProviders = new Set(); + const found = []; + const seen = new Set(); - for (const w of WALLETS) { - try { - const provider = w.detect(); - if (provider && !matchedProviders.has(provider)) { - available.push({ ...w, provider }); - matchedProviders.add(provider); + function add(name, icon, provider, url) { + if (!provider || seen.has(provider)) return; + seen.add(provider); + found.push({ name, icon, provider, url }); + } + + // ── 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 - if (available.length === 0 && window.solana) { - available.push({ - name: 'SOLANA WALLET', - icon: '◈', - provider: window.solana, - url: '#' - }); - } + // ── Wallet Standard discovery (modern wallets) ── + try { + const walletStandard = window.navigator?.wallets; + if (walletStandard && typeof walletStandard[Symbol.iterator] === 'function') { + for (const w of walletStandard) { + if (w.features?.['standard:connect'] && !seen.has(w)) { + 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() { const btn = $('#wallet-btn'); if (!btn) return; @@ -353,54 +373,59 @@ function createWalletModal() { function openModal() { const modal = $('#wallet-modal'); 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 - if (available.length > 0) { - html += '
DETECTED
'; - html += available.map(w => ` - - `).join(''); - } + let html = ''; - // Show not-installed wallets as links (exclude detected ones) - const notInstalled = WALLETS.filter(w => !detectedNames.has(w.name)); - if (notInstalled.length > 0) { - html += '
NOT INSTALLED
'; - html += notInstalled.map(w => ` - - ${w.icon} - ${w.name} - INSTALL ↗ - - `).join(''); - } + if (available.length > 0) { + html += '
DETECTED
'; + html += available.map(w => ` + + `).join(''); + } - if (available.length === 0) { - html = '
NO SOLANA WALLETS DETECTED

Install a Solana wallet extension to connect.
'; - } + const notInstalled = KNOWN_WALLETS.filter(w => !detectedNames.has(w.name)); + if (notInstalled.length > 0) { + html += '
NOT INSTALLED
'; + html += notInstalled.map(w => ` + + ${w.icon} + ${w.name} + INSTALL ↗ + + `).join(''); + } - list.innerHTML = html; + if (available.length === 0) { + html = `
+ NO SOLANA WALLETS DETECTED

+ Install a Solana wallet extension to connect.
+ If you just installed one, refresh the page. +
`; + } - // Bind click handlers only for detected wallets (buttons, not links) - list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => { - btn.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - const name = btn.dataset.wallet; - const wallet = available.find(w => w.name === name); - if (wallet) connectWallet(wallet); + list.innerHTML = html; + + list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + const name = btn.dataset.wallet; + const wallet = available.find(w => w.name === name); + if (wallet) connectWallet(wallet); + }); }); - }); - modal.classList.remove('hidden'); + modal.classList.remove('hidden'); + }, 150); } function closeModal() {