jaeswift-website/js/wallet-connect.js

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();
}
})();