feat: Wallet Standard discovery for MetaMask Solana - event-based registration handshake, dual connect API support
This commit is contained in:
parent
35286a0a39
commit
8d4dec5161
1 changed files with 102 additions and 44 deletions
146
js/soldomains.js
146
js/soldomains.js
|
|
@ -230,17 +230,83 @@ async function doReverse() {
|
||||||
// ─── WALLET ─────────────────────────────────────────────────
|
// ─── WALLET ─────────────────────────────────────────────────
|
||||||
let connectedProvider = null;
|
let connectedProvider = null;
|
||||||
|
|
||||||
|
// ── Wallet Standard Discovery ──
|
||||||
|
// MetaMask Solana and modern wallets register via the Wallet Standard protocol
|
||||||
|
const walletStandardWallets = [];
|
||||||
|
let walletStandardReady = false;
|
||||||
|
|
||||||
|
function initWalletStandard() {
|
||||||
|
// Listen for wallets that register AFTER us
|
||||||
|
window.addEventListener('wallet-standard:register-wallet', (event) => {
|
||||||
|
try {
|
||||||
|
const callback = event.detail;
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback({
|
||||||
|
register: (...wallets) => {
|
||||||
|
for (const w of wallets) {
|
||||||
|
if (!walletStandardWallets.some(existing => existing.name === w.name)) {
|
||||||
|
walletStandardWallets.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(e) { console.warn('WS register error:', e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Announce that the app is ready — catches wallets that loaded BEFORE us
|
||||||
|
try {
|
||||||
|
const appReadyEvent = new CustomEvent('wallet-standard:app-ready', {
|
||||||
|
detail: {
|
||||||
|
register: (...wallets) => {
|
||||||
|
for (const w of wallets) {
|
||||||
|
if (!walletStandardWallets.some(existing => existing.name === w.name)) {
|
||||||
|
walletStandardWallets.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.dispatchEvent(appReadyEvent);
|
||||||
|
} catch(e) { console.warn('WS app-ready error:', e); }
|
||||||
|
|
||||||
|
walletStandardReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise Wallet Standard immediately
|
||||||
|
initWalletStandard();
|
||||||
|
|
||||||
function getAvailableWallets() {
|
function getAvailableWallets() {
|
||||||
const found = [];
|
const found = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
|
||||||
function add(name, icon, provider, url) {
|
function add(name, icon, provider, url, isWalletStandard) {
|
||||||
if (!provider || seen.has(provider)) return;
|
if (!provider || seen.has(name)) return;
|
||||||
seen.add(provider);
|
seen.add(name);
|
||||||
found.push({ name, icon, provider, url });
|
found.push({ name, icon, provider, url, isWalletStandard: !!isWalletStandard });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Specific well-known injection points ──
|
// ── 1. Wallet Standard wallets (MetaMask Solana, etc.) ──
|
||||||
|
for (const w of walletStandardWallets) {
|
||||||
|
try {
|
||||||
|
const hasConnect = w.features?.['standard:connect'];
|
||||||
|
if (!hasConnect) continue;
|
||||||
|
const name = (w.name || 'WALLET').toUpperCase();
|
||||||
|
// Use wallet's icon (base64 data URI) or fallback emoji
|
||||||
|
let icon = '◈';
|
||||||
|
if (name.includes('METAMASK')) icon = '🦊';
|
||||||
|
else if (name.includes('PHANTOM')) icon = '👻';
|
||||||
|
else if (name.includes('SOLFLARE')) icon = '🔆';
|
||||||
|
else if (name.includes('BACKPACK')) icon = '🎒';
|
||||||
|
else if (name.includes('COINBASE')) icon = '🔵';
|
||||||
|
else if (name.includes('TRUST')) icon = '🛡️';
|
||||||
|
else if (name.includes('JUPITER')) icon = '🪐';
|
||||||
|
const url = w.url || '#';
|
||||||
|
add(name, icon, w, url, true);
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Legacy injection points ──
|
||||||
|
|
||||||
// Phantom
|
// Phantom
|
||||||
try {
|
try {
|
||||||
|
|
@ -271,58 +337,35 @@ function getAvailableWallets() {
|
||||||
if (t) add('TRUST', '🛡️', t, 'https://trustwallet.com');
|
if (t) add('TRUST', '🛡️', t, 'https://trustwallet.com');
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// ── Generic window.solana (Jupiter, MetaMask Snap, and others inject here) ──
|
// ── 3. Generic window.solana ──
|
||||||
try {
|
try {
|
||||||
const ws = window.solana;
|
const ws = window.solana;
|
||||||
if (ws && !seen.has(ws)) {
|
if (ws && !seen.has('SOLANA WALLET')) {
|
||||||
// Try to identify what it is
|
if (ws.isJupiter && !seen.has('JUPITER')) add('JUPITER', '🪐', ws, 'https://jup.ag');
|
||||||
if (ws.isJupiter) add('JUPITER', '🪐', ws, 'https://jup.ag');
|
else if (ws.isMetaMask && !seen.has('METAMASK')) add('METAMASK', '🦊', ws, 'https://metamask.io');
|
||||||
else if (ws.isMetaMask) add('METAMASK', '🦊', ws, 'https://metamask.io');
|
else if (ws.isPhantom && !seen.has('PHANTOM')) { /* skip */ }
|
||||||
else if (ws.isPhantom) { /* already caught above */ }
|
else if (ws.isSolflare && !seen.has('SOLFLARE')){ /* skip */ }
|
||||||
else if (ws.isSolflare) { /* already caught above */ }
|
else if (ws.isBackpack && !seen.has('BACKPACK')){ /* skip */ }
|
||||||
else if (ws.isBackpack) { /* already caught above */ }
|
else if (!seen.has('PHANTOM') && !seen.has('METAMASK')) add('SOLANA WALLET', '◈', ws, '#');
|
||||||
else add('SOLANA WALLET', '◈', ws, '#');
|
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// ── Jupiter specific paths ──
|
// ── 4. Jupiter specific ──
|
||||||
try {
|
try {
|
||||||
const j = window.jupiter?.solana;
|
const j = window.jupiter?.solana;
|
||||||
if (j && !seen.has(j)) add('JUPITER', '🪐', j, 'https://jup.ag');
|
if (j) add('JUPITER', '🪐', j, 'https://jup.ag');
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// ── MetaMask ethereum.solana path ──
|
// ── 5. Multi-provider array ──
|
||||||
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 {
|
try {
|
||||||
const providers = window.solana?.providers;
|
const providers = window.solana?.providers;
|
||||||
if (Array.isArray(providers)) {
|
if (Array.isArray(providers)) {
|
||||||
for (const p of providers) {
|
for (const p of providers) {
|
||||||
if (seen.has(p)) continue;
|
|
||||||
if (p.isPhantom) add('PHANTOM', '👻', p, 'https://phantom.app');
|
if (p.isPhantom) add('PHANTOM', '👻', p, 'https://phantom.app');
|
||||||
else if (p.isJupiter) add('JUPITER', '🪐', p, 'https://jup.ag');
|
else if (p.isJupiter) add('JUPITER', '🪐', p, 'https://jup.ag');
|
||||||
else if (p.isSolflare) add('SOLFLARE', '🔆', p, 'https://solflare.com');
|
else if (p.isSolflare) add('SOLFLARE', '🔆', p, 'https://solflare.com');
|
||||||
else if (p.isBackpack) add('BACKPACK', '🎒', p, 'https://backpack.app');
|
else if (p.isBackpack) add('BACKPACK', '🎒', p, 'https://backpack.app');
|
||||||
else if (p.isMetaMask) add('METAMASK', '🦊', p, 'https://metamask.io');
|
else if (p.isMetaMask) add('METAMASK', '🦊', p, 'https://metamask.io');
|
||||||
else add('WALLET', '◈', p, '#');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {}
|
|
||||||
|
|
||||||
// ── 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) {}
|
} catch(e) {}
|
||||||
|
|
@ -330,7 +373,6 @@ function getAvailableWallets() {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All known wallets for install links
|
|
||||||
const KNOWN_WALLETS = [
|
const KNOWN_WALLETS = [
|
||||||
{ name: 'PHANTOM', icon: '👻', url: 'https://phantom.app' },
|
{ name: 'PHANTOM', icon: '👻', url: 'https://phantom.app' },
|
||||||
{ name: 'JUPITER', icon: '🪐', url: 'https://jup.ag' },
|
{ name: 'JUPITER', icon: '🪐', url: 'https://jup.ag' },
|
||||||
|
|
@ -444,10 +486,26 @@ async function toggleWallet() {
|
||||||
async function connectWallet(wallet) {
|
async function connectWallet(wallet) {
|
||||||
try {
|
try {
|
||||||
closeModal();
|
closeModal();
|
||||||
const resp = await wallet.provider.connect();
|
|
||||||
walletAddress = resp.publicKey.toString();
|
if (wallet.isWalletStandard) {
|
||||||
connectedProvider = wallet;
|
// Wallet Standard connect (MetaMask Solana, etc.)
|
||||||
updateWalletUI();
|
const connectFeature = wallet.provider.features['standard:connect'];
|
||||||
|
const result = await connectFeature.connect();
|
||||||
|
const accounts = result.accounts || [];
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
walletAddress = accounts[0].address;
|
||||||
|
connectedProvider = wallet;
|
||||||
|
updateWalletUI();
|
||||||
|
} else {
|
||||||
|
throw new Error('No accounts returned');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Legacy provider connect (Phantom, Solflare, etc.)
|
||||||
|
const resp = await wallet.provider.connect();
|
||||||
|
walletAddress = resp.publicKey.toString();
|
||||||
|
connectedProvider = wallet;
|
||||||
|
updateWalletUI();
|
||||||
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error('Wallet connection failed:', err);
|
console.error('Wallet connection failed:', err);
|
||||||
const status = $('#wallet-status');
|
const status = $('#wallet-status');
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue