268 lines
11 KiB
JavaScript
268 lines
11 KiB
JavaScript
/* ─── Sitewide Solana Wallet Connect ─────────────────────────
|
|
Global wallet connection module for jaeswift.xyz
|
|
Provides window.solWallet API + custom events
|
|
─────────────────────────────────────────────────────────── */
|
|
(function () {
|
|
'use strict';
|
|
|
|
const LS_WALLET_NAME = 'sol_wallet_name';
|
|
const LS_WALLET_ADDR = 'sol_wallet_address';
|
|
|
|
// ── State ──
|
|
const state = {
|
|
connected: false,
|
|
address: null,
|
|
provider: null,
|
|
_walletName: null,
|
|
_isWalletStandard: false,
|
|
};
|
|
|
|
// ── Wallet Standard Discovery ──
|
|
const walletStandardWallets = [];
|
|
|
|
function initWalletStandard() {
|
|
// Listen for wallets registering 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(e => e.name === w.name)) {
|
|
walletStandardWallets.push(w);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (e) { /* silent */ }
|
|
});
|
|
|
|
// Announce app-ready — catches wallets loaded BEFORE us
|
|
try {
|
|
window.dispatchEvent(new CustomEvent('wallet-standard:app-ready', {
|
|
detail: {
|
|
register: (...wallets) => {
|
|
for (const w of wallets) {
|
|
if (!walletStandardWallets.some(e => e.name === w.name)) {
|
|
walletStandardWallets.push(w);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
} catch (e) { /* silent */ }
|
|
}
|
|
|
|
initWalletStandard();
|
|
|
|
// ── 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' },
|
|
];
|
|
|
|
// ── Wallet detection (reused from soldomains.js) ──
|
|
function getAvailableWallets() {
|
|
const found = [];
|
|
const seen = new Set();
|
|
|
|
function add(name, icon, provider, url, isWS) {
|
|
if (!provider || seen.has(name)) return;
|
|
seen.add(name);
|
|
found.push({ name, icon, provider, url, isWalletStandard: !!isWS });
|
|
}
|
|
|
|
// 1. Wallet Standard wallets
|
|
for (const w of walletStandardWallets) {
|
|
try {
|
|
if (!w.features?.['standard:connect']) continue;
|
|
const name = (w.name || 'WALLET').toUpperCase();
|
|
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 = '🪐';
|
|
add(name, icon, w, w.url || '#', true);
|
|
} catch (e) { /* silent */ }
|
|
}
|
|
|
|
// 2. Legacy injection points
|
|
try { const p = window.phantom?.solana; if (p && p.isPhantom) add('PHANTOM', '👻', p, 'https://phantom.app'); } catch (e) {}
|
|
try { const s = window.solflare; if (s && s.isSolflare) add('SOLFLARE', '🔆', s, 'https://solflare.com'); } catch (e) {}
|
|
try { const b = window.backpack; if (b && b.isBackpack) add('BACKPACK', '🎒', b, 'https://backpack.app'); } catch (e) {}
|
|
try { if (window.coinbaseSolana) add('COINBASE', '🔵', window.coinbaseSolana, 'https://coinbase.com/wallet'); } catch (e) {}
|
|
try { const t = window.trustwallet?.solana; if (t) add('TRUST', '🛡️', t, 'https://trustwallet.com'); } catch (e) {}
|
|
|
|
// 3. Generic window.solana
|
|
try {
|
|
const ws = window.solana;
|
|
if (ws) {
|
|
if (ws.isJupiter && !seen.has('JUPITER')) add('JUPITER', '🪐', ws, 'https://jup.ag');
|
|
else if (ws.isMetaMask && !seen.has('METAMASK')) add('METAMASK', '🦊', ws, 'https://metamask.io');
|
|
else if (!ws.isPhantom && !ws.isSolflare && !ws.isBackpack && !seen.has('PHANTOM') && !seen.has('METAMASK'))
|
|
add('SOLANA WALLET', '◈', ws, '#');
|
|
}
|
|
} catch (e) {}
|
|
|
|
// 4. Jupiter specific
|
|
try { const j = window.jupiter?.solana; if (j) add('JUPITER', '🪐', j, 'https://jup.ag'); } catch (e) {}
|
|
|
|
// 5. Multi-provider array
|
|
try {
|
|
const providers = window.solana?.providers;
|
|
if (Array.isArray(providers)) {
|
|
for (const p of providers) {
|
|
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');
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
|
|
return found;
|
|
}
|
|
|
|
// ── Connect ──
|
|
async function connect(walletObj) {
|
|
// If called without arg, open picker (handled by nav UI)
|
|
// If called with a wallet name string, find it
|
|
if (typeof walletObj === 'string') {
|
|
const available = getAvailableWallets();
|
|
walletObj = available.find(w => w.name === walletObj);
|
|
if (!walletObj) throw new Error('Wallet not found: ' + walletObj);
|
|
}
|
|
if (!walletObj) throw new Error('No wallet specified');
|
|
|
|
try {
|
|
let address;
|
|
if (walletObj.isWalletStandard) {
|
|
const connectFeature = walletObj.provider.features['standard:connect'];
|
|
const result = await connectFeature.connect();
|
|
const accounts = result.accounts || [];
|
|
if (accounts.length === 0) throw new Error('No accounts returned');
|
|
address = accounts[0].address;
|
|
} else {
|
|
const resp = await walletObj.provider.connect();
|
|
address = resp.publicKey.toString();
|
|
}
|
|
|
|
state.connected = true;
|
|
state.address = address;
|
|
state.provider = walletObj.provider;
|
|
state._walletName = walletObj.name;
|
|
state._isWalletStandard = walletObj.isWalletStandard;
|
|
|
|
// Persist
|
|
localStorage.setItem(LS_WALLET_NAME, walletObj.name);
|
|
localStorage.setItem(LS_WALLET_ADDR, address);
|
|
|
|
// Dispatch event
|
|
window.dispatchEvent(new CustomEvent('wallet-connected', {
|
|
detail: { address, walletName: walletObj.name }
|
|
}));
|
|
|
|
return { address, walletName: walletObj.name };
|
|
} catch (err) {
|
|
console.warn('[solWallet] Connect failed:', err.message);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// ── Disconnect ──
|
|
function disconnect() {
|
|
if (state.provider) {
|
|
try {
|
|
if (state._isWalletStandard) {
|
|
const disconnectFeature = state.provider.features?.['standard:disconnect'];
|
|
if (disconnectFeature) disconnectFeature.disconnect();
|
|
} else {
|
|
state.provider.disconnect();
|
|
}
|
|
} catch (e) { /* silent */ }
|
|
}
|
|
|
|
const prevAddr = state.address;
|
|
state.connected = false;
|
|
state.address = null;
|
|
state.provider = null;
|
|
state._walletName = null;
|
|
state._isWalletStandard = false;
|
|
|
|
localStorage.removeItem(LS_WALLET_NAME);
|
|
localStorage.removeItem(LS_WALLET_ADDR);
|
|
|
|
window.dispatchEvent(new CustomEvent('wallet-disconnected', {
|
|
detail: { address: prevAddr }
|
|
}));
|
|
}
|
|
|
|
// ── Silent auto-reconnect ──
|
|
async function autoReconnect() {
|
|
const savedName = localStorage.getItem(LS_WALLET_NAME);
|
|
if (!savedName) return;
|
|
|
|
// Small delay to let wallet extensions inject
|
|
await new Promise(r => setTimeout(r, 600));
|
|
|
|
const available = getAvailableWallets();
|
|
const wallet = available.find(w => w.name === savedName);
|
|
if (!wallet) {
|
|
// Wallet no longer available — clear saved state quietly
|
|
localStorage.removeItem(LS_WALLET_NAME);
|
|
localStorage.removeItem(LS_WALLET_ADDR);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await connect(wallet);
|
|
} catch (e) {
|
|
// Auto-reconnect failed — clear saved state quietly
|
|
localStorage.removeItem(LS_WALLET_NAME);
|
|
localStorage.removeItem(LS_WALLET_ADDR);
|
|
state.connected = false;
|
|
state.address = null;
|
|
state.provider = null;
|
|
state._walletName = null;
|
|
}
|
|
}
|
|
|
|
// ── Truncate address helper ──
|
|
function truncAddr(a) {
|
|
if (!a || a.length < 12) return a || '';
|
|
return a.slice(0, 4) + '...' + a.slice(-4);
|
|
}
|
|
|
|
// ── Public API ──
|
|
window.solWallet = {
|
|
get connected() { return state.connected; },
|
|
get address() { return state.address; },
|
|
get provider() { return state.provider; },
|
|
get walletName() { return state._walletName; },
|
|
get isWalletStandard() { return state._isWalletStandard; },
|
|
connect,
|
|
disconnect,
|
|
getAvailableWallets,
|
|
truncAddr,
|
|
KNOWN_WALLETS,
|
|
_autoReconnect: autoReconnect,
|
|
};
|
|
|
|
// Kick off auto-reconnect when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', autoReconnect);
|
|
} else {
|
|
autoReconnect();
|
|
}
|
|
})();
|