From 4fdd4aaf1c2041fbe00a05a49ad4f74b4b35ce78 Mon Sep 17 00:00:00 2001 From: jae Date: Mon, 6 Apr 2026 00:05:30 +0000 Subject: [PATCH] feat: sitewide Solana wallet connect in navbar --- armoury/debrief.html | 1 + armoury/deployments.html | 1 + armoury/fieldmanuals.html | 1 + armoury/index.html | 1 + armoury/lab.html | 1 + blog.html | 1 + comms/backuprelay.html | 1 + comms/encryptedline.html | 1 + comms/index.html | 1 + comms/openchannels.html | 1 + css/style.css | 288 ++++++++++++++++++++++++++++++++++ depot/contraband.html | 1 + depot/exfil.html | 1 + depot/index.html | 1 + depot/propaganda.html | 1 + depot/recon.html | 1 + hq/briefing.html | 1 + hq/index.html | 1 + hq/logs.html | 1 + hq/profile.html | 1 + hq/telemetry.html | 1 + index.html | 1 + js/nav.js | 223 +++++++++++++++++++++++++- js/soldomains.js | 248 ++++++----------------------- js/wallet-connect.js | 268 +++++++++++++++++++++++++++++++ post.html | 1 + recon/awesomelist.html | 1 + soldomains/index.html | 1 + transmissions/dispatches.html | 1 + transmissions/index.html | 1 + transmissions/radar.html | 1 + transmissions/sitrep.html | 1 + 32 files changed, 857 insertions(+), 198 deletions(-) create mode 100644 js/wallet-connect.js diff --git a/armoury/debrief.html b/armoury/debrief.html index e51d022..d715afc 100644 --- a/armoury/debrief.html +++ b/armoury/debrief.html @@ -63,6 +63,7 @@ + diff --git a/armoury/deployments.html b/armoury/deployments.html index db9da95..68622f6 100644 --- a/armoury/deployments.html +++ b/armoury/deployments.html @@ -139,6 +139,7 @@ + diff --git a/armoury/fieldmanuals.html b/armoury/fieldmanuals.html index af259b9..5fa6d4f 100644 --- a/armoury/fieldmanuals.html +++ b/armoury/fieldmanuals.html @@ -63,6 +63,7 @@ + diff --git a/armoury/index.html b/armoury/index.html index 1491512..f3c9c4b 100644 --- a/armoury/index.html +++ b/armoury/index.html @@ -75,6 +75,7 @@ + diff --git a/armoury/lab.html b/armoury/lab.html index 1252a12..16d2af1 100644 --- a/armoury/lab.html +++ b/armoury/lab.html @@ -140,6 +140,7 @@ + diff --git a/blog.html b/blog.html index 80e7ded..1ab2561 100644 --- a/blog.html +++ b/blog.html @@ -76,6 +76,7 @@ + diff --git a/comms/backuprelay.html b/comms/backuprelay.html index 9112e6e..b5c67fa 100644 --- a/comms/backuprelay.html +++ b/comms/backuprelay.html @@ -63,6 +63,7 @@ + diff --git a/comms/encryptedline.html b/comms/encryptedline.html index 4f37aba..6a91b60 100644 --- a/comms/encryptedline.html +++ b/comms/encryptedline.html @@ -63,6 +63,7 @@ + diff --git a/comms/index.html b/comms/index.html index be6c114..bcfc3ae 100644 --- a/comms/index.html +++ b/comms/index.html @@ -69,6 +69,7 @@ + diff --git a/comms/openchannels.html b/comms/openchannels.html index 808c137..75515f1 100644 --- a/comms/openchannels.html +++ b/comms/openchannels.html @@ -63,6 +63,7 @@ + diff --git a/css/style.css b/css/style.css index 94b6b12..e39889d 100644 --- a/css/style.css +++ b/css/style.css @@ -328,6 +328,265 @@ a:hover { color: #fff; text-shadow: none; } transition: all 0.3s ease; } + +/* === NAV WALLET BUTTON === */ +.nav-wallet-wrap { + position: relative; + display: flex; + align-items: center; + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.nav-wallet-btn { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.35rem 0.85rem; + font-family: var(--font-mono); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 1.5px; + color: var(--warning); + background: rgba(201, 162, 39, 0.08); + border: 1px solid rgba(201, 162, 39, 0.3); + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; +} + +.nav-wallet-btn:hover { + background: rgba(201, 162, 39, 0.15); + border-color: rgba(201, 162, 39, 0.5); + color: #e0c040; +} + +.nav-wallet-btn.connected { + color: #14F195; + background: rgba(20, 241, 149, 0.06); + border-color: rgba(20, 241, 149, 0.25); +} + +.nav-wallet-btn.connected:hover { + background: rgba(20, 241, 149, 0.12); + border-color: rgba(20, 241, 149, 0.4); +} + +.nav-wallet-icon { + font-size: 0.65rem; +} + +.nav-wallet-dot { + color: #14F195; + font-size: 0.55rem; + text-shadow: 0 0 6px rgba(20, 241, 149, 0.6); +} + +.nav-wallet-label { + font-family: var(--font-mono); +} + +/* Wallet Dropdown */ +.nav-wallet-dropdown { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 280px; + max-width: 340px; + background: rgba(14, 14, 14, 0.98); + border: 1px solid var(--border); + border-top: 2px solid #14F195; + backdrop-filter: blur(20px); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6); + z-index: 1100; + padding: 0.5rem 0; + animation: nwDropIn 0.2s ease; +} + +.nav-wallet-dropdown.hidden { + display: none; +} + +@keyframes nwDropIn { + from { opacity: 0; transform: translateY(-6px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Dropdown labels */ +.nw-dd-label { + font-family: var(--font-display); + font-size: 0.6rem; + letter-spacing: 2px; + color: var(--text-muted); + padding: 0.5rem 0.85rem 0.35rem; +} + +.nw-dd-label-dim { + color: #444; +} + +/* Connected section */ +.nw-dd-section { + padding: 0.5rem 0.85rem; +} + +.nw-dd-addr { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.5rem 0.6rem; + margin: 0.35rem 0; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--border); + cursor: pointer; + transition: all 0.2s; +} + +.nw-dd-addr:hover { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(20, 241, 149, 0.3); +} + +.nw-dd-addr-text { + font-family: var(--font-mono); + font-size: 0.6rem; + color: var(--text-primary); + word-break: break-all; + line-height: 1.4; +} + +.nw-dd-copy-icon { + font-size: 0.8rem; + color: var(--text-muted); + flex-shrink: 0; +} + +.nw-dd-via { + font-family: var(--font-mono); + font-size: 0.55rem; + color: var(--text-muted); + letter-spacing: 1px; +} + +/* Dropdown actions */ +.nw-dd-actions { + border-top: 1px solid var(--border); + margin-top: 0.25rem; +} + +.nw-dd-action { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.6rem 0.85rem; + font-family: var(--font-mono); + font-size: 0.65rem; + letter-spacing: 1px; + color: var(--text-secondary); + background: none; + border: none; + border-bottom: 1px solid var(--border); + cursor: pointer; + transition: all 0.2s; + text-decoration: none; +} + +.nw-dd-action:last-child { + border-bottom: none; +} + +.nw-dd-action:hover { + background: rgba(255, 255, 255, 0.03); + color: #fff; + padding-left: 1rem; +} + +.nw-dd-disconnect:hover { + color: var(--danger); +} + +/* Wallet picker items */ +.nw-dd-wallet { + display: flex; + align-items: center; + gap: 0.6rem; + width: 100%; + padding: 0.55rem 0.85rem; + font-family: var(--font-mono); + font-size: 0.65rem; + color: var(--text-secondary); + background: none; + border: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); + cursor: pointer; + transition: all 0.2s; + text-decoration: none; +} + +.nw-dd-wallet:hover { + background: rgba(255, 255, 255, 0.04); + color: #fff; +} + +.nw-dd-wallet:last-child { + border-bottom: none; +} + +.nw-dd-wallet-icon { + font-size: 1rem; + flex-shrink: 0; + width: 1.4rem; + text-align: center; +} + +.nw-dd-wallet-name { + flex: 1; + letter-spacing: 1.5px; + font-weight: 500; +} + +.nw-dd-wallet-tag { + font-size: 0.5rem; + letter-spacing: 1px; + color: #14F195; + flex-shrink: 0; +} + +.nw-dd-wallet-install .nw-dd-wallet-tag { + color: var(--text-muted); +} + +.nw-dd-wallet-install .nw-dd-wallet-name { + color: var(--text-muted); +} + +.nw-dd-error { + color: var(--danger) !important; +} + +.nw-dd-divider { + height: 1px; + background: var(--border); + margin: 0.35rem 0; +} + +.nw-dd-empty { + padding: 1rem 0.85rem; + font-family: var(--font-mono); + font-size: 0.65rem; + color: var(--text-muted); + text-align: center; + letter-spacing: 1px; + line-height: 1.6; +} + +.nw-dd-empty-hint { + font-size: 0.55rem; + color: #14F195; +} + /* === HERO SECTION === */ /* ============================ HERO HUD GRID LAYOUT @@ -1629,6 +1888,35 @@ a:hover { color: #fff; text-shadow: none; } opacity: 1; } + /* Wallet button mobile */ + .nav-wallet-wrap { + position: static; + width: 100%; + padding: 0.75rem 2rem; + border-top: 1px solid var(--border); + margin: 0; + } + + .nav-wallet-btn { + width: 100%; + justify-content: center; + padding: 0.6rem 1rem; + font-size: 0.75rem; + } + + .nav-wallet-dropdown { + position: static; + min-width: 100%; + max-width: 100%; + border: none; + border-top: 1px solid var(--border); + box-shadow: none; + backdrop-filter: none; + background: rgba(14, 14, 14, 0.95); + animation: none; + } + + /* HUD grid stacks on mobile */ .hero-hud-grid { diff --git a/depot/contraband.html b/depot/contraband.html index b8bfe73..b9e771d 100644 --- a/depot/contraband.html +++ b/depot/contraband.html @@ -62,6 +62,7 @@ + diff --git a/depot/exfil.html b/depot/exfil.html index fa0f681..ae72722 100644 --- a/depot/exfil.html +++ b/depot/exfil.html @@ -63,6 +63,7 @@ + diff --git a/depot/index.html b/depot/index.html index 6d5304e..2d059e0 100644 --- a/depot/index.html +++ b/depot/index.html @@ -75,6 +75,7 @@ + diff --git a/depot/propaganda.html b/depot/propaganda.html index e4cd638..b066b50 100644 --- a/depot/propaganda.html +++ b/depot/propaganda.html @@ -44,6 +44,7 @@ + diff --git a/depot/recon.html b/depot/recon.html index cd53217..29c4fcc 100644 --- a/depot/recon.html +++ b/depot/recon.html @@ -59,6 +59,7 @@ + diff --git a/hq/briefing.html b/hq/briefing.html index 5991f3f..124dbd4 100644 --- a/hq/briefing.html +++ b/hq/briefing.html @@ -63,6 +63,7 @@ + diff --git a/hq/index.html b/hq/index.html index 9d04fe1..5b9f588 100644 --- a/hq/index.html +++ b/hq/index.html @@ -75,6 +75,7 @@ + diff --git a/hq/logs.html b/hq/logs.html index 1d58960..8db078b 100644 --- a/hq/logs.html +++ b/hq/logs.html @@ -49,6 +49,7 @@ + diff --git a/hq/profile.html b/hq/profile.html index 1feabad..d120ce4 100644 --- a/hq/profile.html +++ b/hq/profile.html @@ -63,6 +63,7 @@ + diff --git a/hq/telemetry.html b/hq/telemetry.html index 499044f..7037bbd 100644 --- a/hq/telemetry.html +++ b/hq/telemetry.html @@ -63,6 +63,7 @@ + diff --git a/index.html b/index.html index c1168bb..264fb0e 100644 --- a/index.html +++ b/index.html @@ -582,6 +582,7 @@ + diff --git a/js/nav.js b/js/nav.js index ed3aae4..62a9e11 100644 --- a/js/nav.js +++ b/js/nav.js @@ -1,5 +1,6 @@ /* ─── Dynamic Navigation with Dropdown Submenus ─── */ /* Fetches nav items from /api/navigation and renders dropdowns */ +/* Includes sitewide wallet connect button via window.solWallet */ (function() { 'use strict'; @@ -84,6 +85,9 @@ // Bind mobile dropdown toggles after rendering initMobileDropdowns(); + + // Inject wallet button after nav loads + injectWalletButton(); } catch (err) { console.warn('Nav load failed, keeping existing:', err); } @@ -111,7 +115,6 @@ parent.classList.add('dropdown-open'); } else { // Dropdown already open — allow navigation or close - // If tapped again on same parent link, close dropdown e.preventDefault(); parent.classList.remove('dropdown-open'); } @@ -147,6 +150,224 @@ } } + // ─── Wallet Button Injection ──────────────────────────────── + function injectWalletButton() { + const navContainer = document.querySelector('.nav-container'); + const navStatus = document.querySelector('.nav-status'); + if (!navContainer) return; + + // Don't inject twice + if (document.getElementById('navWalletWrap')) return; + + // Create wallet wrapper + const wrap = document.createElement('div'); + wrap.id = 'navWalletWrap'; + wrap.className = 'nav-wallet-wrap'; + wrap.innerHTML = ` + + + `; + + // Insert before nav-status (or append to container) + if (navStatus) { + navContainer.insertBefore(wrap, navStatus); + } else { + navContainer.appendChild(wrap); + } + + // Bind click + const btn = document.getElementById('navWalletBtn'); + btn.addEventListener('click', (e) => { + e.stopPropagation(); + toggleWalletDropdown(); + }); + + // Close dropdown on outside click + document.addEventListener('click', (e) => { + if (!e.target.closest('#navWalletWrap')) { + closeWalletDropdown(); + } + }); + + // Listen for wallet events + window.addEventListener('wallet-connected', () => updateNavWalletUI()); + window.addEventListener('wallet-disconnected', () => updateNavWalletUI()); + + // Set initial state + updateNavWalletUI(); + } + + function updateNavWalletUI() { + const btn = document.getElementById('navWalletBtn'); + if (!btn) return; + + const sw = window.solWallet; + if (sw && sw.connected && sw.address) { + btn.className = 'nav-wallet-btn connected'; + btn.innerHTML = ` + + ${sw.truncAddr(sw.address)} + `; + } else { + btn.className = 'nav-wallet-btn'; + btn.innerHTML = ` + + CONNECT + `; + } + + // Close dropdown on state change + closeWalletDropdown(); + } + + function toggleWalletDropdown() { + const dd = document.getElementById('navWalletDropdown'); + if (!dd) return; + + if (dd.classList.contains('hidden')) { + openWalletDropdown(); + } else { + closeWalletDropdown(); + } + } + + function openWalletDropdown() { + const dd = document.getElementById('navWalletDropdown'); + if (!dd) return; + + const sw = window.solWallet; + if (!sw) { + dd.innerHTML = '
WALLET MODULE NOT LOADED
'; + dd.classList.remove('hidden'); + return; + } + + if (sw.connected && sw.address) { + // ── Connected dropdown: address, copy, solscan, disconnect ── + const addr = sw.address; + const solscan = 'https://solscan.io/account/' + addr; + dd.innerHTML = ` +
+
CONNECTED WALLET
+
+ ${addr} + +
+
VIA ${sw.walletName || 'WALLET'}
+
+
+ + SOLSCAN + + +
+ `; + + // Copy address + dd.querySelector('#nwCopyAddr').addEventListener('click', () => { + navigator.clipboard.writeText(addr).then(() => { + const el = dd.querySelector('#nwCopyAddr .nw-dd-copy-icon'); + if (el) { el.textContent = '✓'; setTimeout(() => el.textContent = '⧉', 1500); } + }); + }); + + // Disconnect + dd.querySelector('#nwDisconnect').addEventListener('click', () => { + sw.disconnect(); + }); + } else { + // ── Disconnected dropdown: wallet picker ── + // Short delay for wallet detection + setTimeout(() => { + const available = sw.getAvailableWallets(); + const detectedNames = new Set(available.map(w => w.name)); + let html = ''; + + if (available.length > 0) { + html += '
SELECT WALLET
'; + html += available.map(w => ` + + `).join(''); + } + + // Show install links for missing wallets + const missing = sw.KNOWN_WALLETS.filter(w => !detectedNames.has(w.name)); + if (missing.length > 0 && available.length < 3) { + html += '
'; + html += '
INSTALL
'; + html += missing.slice(0, 3).map(w => ` + + ${w.icon} + ${w.name} + INSTALL ↗ + + `).join(''); + } + + if (available.length === 0) { + html = ` +
+ NO WALLETS DETECTED
+ Install a Solana wallet extension +
+ `; + // Still show install links + html += sw.KNOWN_WALLETS.slice(0, 4).map(w => ` + + ${w.icon} + ${w.name} + INSTALL ↗ + + `).join(''); + } + + dd.innerHTML = html; + + // Bind detected wallet clicks + dd.querySelectorAll('.nw-dd-wallet[data-wallet]').forEach(el => { + el.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + const name = el.dataset.wallet; + const wallet = available.find(w => w.name === name); + if (!wallet) return; + + // Show connecting state + el.querySelector('.nw-dd-wallet-tag').textContent = 'CONNECTING...'; + try { + await sw.connect(wallet); + } catch (err) { + el.querySelector('.nw-dd-wallet-tag').textContent = 'REJECTED'; + el.querySelector('.nw-dd-wallet-tag').classList.add('nw-dd-error'); + setTimeout(() => { + if (el.querySelector('.nw-dd-wallet-tag')) { + el.querySelector('.nw-dd-wallet-tag').textContent = 'DETECTED'; + el.querySelector('.nw-dd-wallet-tag').classList.remove('nw-dd-error'); + } + }, 2000); + } + }); + }); + }, 150); + } + + dd.classList.remove('hidden'); + } + + function closeWalletDropdown() { + const dd = document.getElementById('navWalletDropdown'); + if (dd) dd.classList.add('hidden'); + } + // Run on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { diff --git a/js/soldomains.js b/js/soldomains.js index 49748cf..537b07c 100644 --- a/js/soldomains.js +++ b/js/soldomains.js @@ -234,167 +234,39 @@ async function doReverse() { } } -// ─── WALLET ───────────────────────────────────────────────── -let connectedProvider = null; +// ─── WALLET (uses global window.solWallet from wallet-connect.js) ──────────── -// ── 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() { - const found = []; - const seen = new Set(); - - function add(name, icon, provider, url, isWalletStandard) { - if (!provider || seen.has(name)) return; - seen.add(name); - found.push({ name, icon, provider, url, isWalletStandard: !!isWalletStandard }); +// Sync local walletAddress from global wallet state +function syncWalletState() { + const sw = window.solWallet; + if (sw && sw.connected && sw.address) { + walletAddress = sw.address; + } else { + walletAddress = null; } - - // ── 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 - 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) {} - - // ── 3. Generic window.solana ── - try { - const ws = window.solana; - if (ws && !seen.has('SOLANA WALLET')) { - 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 && !seen.has('PHANTOM')) { /* skip */ } - else if (ws.isSolflare && !seen.has('SOLFLARE')){ /* skip */ } - else if (ws.isBackpack && !seen.has('BACKPACK')){ /* skip */ } - else if (!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; } -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; btn.addEventListener('click', toggleWallet); createWalletModal(); + + // Listen for global wallet events + window.addEventListener('wallet-connected', (e) => { + walletAddress = e.detail.address; + updateWalletUI(); + checkPendingRegistration(); + }); + + window.addEventListener('wallet-disconnected', () => { + walletAddress = null; + updateWalletUI(); + }); + + // Sync initial state (wallet may have auto-reconnected already) + syncWalletState(); + updateWalletUI(); } function createWalletModal() { @@ -420,12 +292,17 @@ function createWalletModal() { } function openModal() { + const sw = window.solWallet; + if (!sw) { console.warn('solWallet not loaded'); return; } + const modal = $('#wallet-modal'); const list = $('#wallet-list'); + const KNOWN_WALLETS = sw.KNOWN_WALLETS || []; + // Small delay to let async provider injection complete setTimeout(() => { - const available = getAvailableWallets(); + const available = sw.getAvailableWallets(); const detectedNames = new Set(available.map(w => w.name)); let html = ''; @@ -464,12 +341,21 @@ function openModal() { list.innerHTML = html; list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => { - btn.addEventListener('click', (e) => { + btn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const name = btn.dataset.wallet; const wallet = available.find(w => w.name === name); - if (wallet) connectWallet(wallet); + if (wallet) { + try { + closeModal(); + await sw.connect(wallet); + } catch (err) { + console.error('Wallet connection failed:', err); + const status = $('#wallet-status'); + if (status) status.innerHTML = `CONNECTION REJECTED`; + } + } }); }); @@ -490,44 +376,10 @@ async function toggleWallet() { openModal(); } -async function connectWallet(wallet) { - try { - closeModal(); - - if (wallet.isWalletStandard) { - // Wallet Standard connect (MetaMask Solana, etc.) - 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(); - checkPendingRegistration(); - } 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(); - checkPendingRegistration(); - } - } catch(err) { - console.error('Wallet connection failed:', err); - const status = $('#wallet-status'); - if (status) status.innerHTML = `CONNECTION REJECTED`; - } -} - function disconnectWallet() { - if (connectedProvider && connectedProvider.provider) { - try { connectedProvider.provider.disconnect(); } catch(e) {} - } + const sw = window.solWallet; + if (sw) sw.disconnect(); walletAddress = null; - connectedProvider = null; updateWalletUI(); } @@ -535,9 +387,11 @@ function updateWalletUI() { const status = $('#wallet-status'); const btn = $('#wallet-btn'); + const sw = window.solWallet; + if (walletAddress) { - const wName = connectedProvider ? connectedProvider.name : 'WALLET'; - const wIcon = connectedProvider ? connectedProvider.icon : '◈'; + const wName = (sw && sw.walletName) ? sw.walletName : 'WALLET'; + const wIcon = '◆'; status.className = 'sol-wallet-status connected'; status.innerHTML = `● CONNECTED VIA ${wIcon} ${wName} ${truncAddr(walletAddress)}`; btn.className = 'sol-wallet-btn disconnect'; @@ -784,7 +638,7 @@ function updateRegistrationStatus(state, message, extra) { } async function executeRegistration(domain) { - if (!walletAddress || !connectedProvider) { + if (!walletAddress || !window.solWallet?.provider) { updateRegistrationStatus('error', 'WALLET NOT CONNECTED', '

Please connect your wallet and try again.

'); return; @@ -854,7 +708,7 @@ async function executeRegistration(domain) { // 6. Sign and send via connected wallet let signature; - if (connectedProvider.isWalletStandard) { + if (window.solWallet && window.solWallet.isWalletStandard) { signature = await signAndSendWalletStandard(transaction, isVersioned, connection); } else { signature = await signAndSendLegacy(transaction, isVersioned, connection); @@ -923,7 +777,7 @@ async function executeRegistration(domain) { } async function signAndSendLegacy(transaction, isVersioned, connection) { - const provider = connectedProvider.provider; + const provider = window.solWallet.provider; // Try signAndSendTransaction first (Phantom, Solflare support this) if (typeof provider.signAndSendTransaction === 'function') { @@ -953,7 +807,7 @@ async function signAndSendLegacy(transaction, isVersioned, connection) { } async function signAndSendWalletStandard(transaction, isVersioned, connection) { - const wallet = connectedProvider.provider; + const wallet = window.solWallet.provider; const account = wallet.accounts?.[0]; if (!account) throw new Error('No wallet account available.'); diff --git a/js/wallet-connect.js b/js/wallet-connect.js new file mode 100644 index 0000000..4cae8f8 --- /dev/null +++ b/js/wallet-connect.js @@ -0,0 +1,268 @@ +/* ─── 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(); + } +})(); diff --git a/post.html b/post.html index b1c8558..b5c5dd5 100644 --- a/post.html +++ b/post.html @@ -110,6 +110,7 @@ + diff --git a/recon/awesomelist.html b/recon/awesomelist.html index efb7db5..21a8834 100644 --- a/recon/awesomelist.html +++ b/recon/awesomelist.html @@ -61,6 +61,7 @@ + diff --git a/soldomains/index.html b/soldomains/index.html index fd11727..6782895 100644 --- a/soldomains/index.html +++ b/soldomains/index.html @@ -113,6 +113,7 @@ + diff --git a/transmissions/dispatches.html b/transmissions/dispatches.html index 1998443..fc2e62e 100644 --- a/transmissions/dispatches.html +++ b/transmissions/dispatches.html @@ -75,6 +75,7 @@ + diff --git a/transmissions/index.html b/transmissions/index.html index 66ffbef..2315ee4 100644 --- a/transmissions/index.html +++ b/transmissions/index.html @@ -69,6 +69,7 @@ + diff --git a/transmissions/radar.html b/transmissions/radar.html index 7e70dc6..66ba2c3 100644 --- a/transmissions/radar.html +++ b/transmissions/radar.html @@ -84,6 +84,7 @@ + diff --git a/transmissions/sitrep.html b/transmissions/sitrep.html index 800767b..0b045dd 100644 --- a/transmissions/sitrep.html +++ b/transmissions/sitrep.html @@ -63,6 +63,7 @@ +