feat: multi-wallet support - Phantom, Solflare, Backpack, Coinbase, Trust, MetaMask

This commit is contained in:
jae 2026-04-05 19:06:24 +00:00
parent d0d13c3f30
commit b71361d7ab
2 changed files with 266 additions and 11 deletions

View file

@ -446,3 +446,155 @@
font-size: 0.6rem;
color: rgba(255, 255, 255, 0.3);
}
/* Wallet Modal */
.sol-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.sol-modal.hidden {
display: none !important;
}
.sol-modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(4px);
}
.sol-modal-content {
position: relative;
background: rgba(12, 12, 12, 0.98);
border: 1px solid rgba(138, 43, 226, 0.3);
border-top: 2px solid #a855f7;
width: 90%;
max-width: 420px;
max-height: 80vh;
overflow-y: auto;
}
.sol-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid rgba(138, 43, 226, 0.15);
}
.sol-modal-title {
font-family: 'Orbitron', monospace;
font-size: 0.7rem;
color: #a855f7;
letter-spacing: 3px;
}
.sol-modal-close {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.5);
font-size: 0.75rem;
width: 28px;
height: 28px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.sol-modal-close:hover {
border-color: rgba(255, 50, 50, 0.5);
color: rgba(255, 50, 50, 0.8);
}
.sol-modal-body {
padding: 0.75rem;
}
.sol-modal-footer {
padding: 0.75rem 1.25rem;
border-top: 1px solid rgba(138, 43, 226, 0.1);
}
.sol-modal-hint {
font-family: 'JetBrains Mono', monospace;
font-size: 0.55rem;
color: rgba(255, 255, 255, 0.25);
letter-spacing: 1px;
}
/* Wallet Options */
.sol-wallet-option {
display: flex;
align-items: center;
gap: 0.75rem;
width: 100%;
padding: 0.85rem 1rem;
background: rgba(20, 20, 20, 0.6);
border: 1px solid rgba(138, 43, 226, 0.12);
color: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 0.4rem;
text-decoration: none;
}
.sol-wallet-option:hover {
background: rgba(138, 43, 226, 0.1);
border-color: rgba(138, 43, 226, 0.35);
transform: translateX(3px);
}
.sol-wallet-option.not-installed {
opacity: 0.45;
}
.sol-wallet-option.not-installed:hover {
opacity: 0.7;
}
.sol-wallet-option-icon {
font-size: 1.2rem;
width: 28px;
text-align: center;
}
.sol-wallet-option-name {
font-family: 'Orbitron', monospace;
font-size: 0.65rem;
letter-spacing: 2px;
flex: 1;
}
.sol-wallet-option-status {
font-family: 'JetBrains Mono', monospace;
font-size: 0.5rem;
letter-spacing: 1px;
color: #00cc44;
}
.sol-wallet-option.not-installed .sol-wallet-option-status {
color: rgba(255, 255, 255, 0.3);
}
.sol-wallet-divider {
font-family: 'JetBrains Mono', monospace;
font-size: 0.5rem;
color: rgba(255, 255, 255, 0.2);
letter-spacing: 2px;
padding: 0.75rem 1rem 0.4rem;
border-top: 1px solid rgba(255, 255, 255, 0.05);
margin-top: 0.25rem;
}

View file

@ -244,10 +244,109 @@ async function doReverse() {
}
// ─── WALLET ─────────────────────────────────────────────────
let connectedProvider = null;
const WALLETS = [
{ name: 'PHANTOM', icon: '👻', detect: () => window.phantom?.solana, url: 'https://phantom.app' },
{ name: 'SOLFLARE', icon: '🔆', detect: () => window.solflare, url: 'https://solflare.com' },
{ name: 'BACKPACK', icon: '🎒', detect: () => window.backpack, url: 'https://backpack.app' },
{ name: 'COINBASE', icon: '🔵', detect: () => window.coinbaseSolana, url: 'https://coinbase.com/wallet' },
{ name: 'TRUST', icon: '🛡️', detect: () => window.trustwallet?.solana, url: 'https://trustwallet.com' },
{ name: 'METAMASK', icon: '🦊', detect: () => window.ethereum?.isSolana ? window.ethereum : null, url: 'https://metamask.io' },
];
function getAvailableWallets() {
const available = [];
for (const w of WALLETS) {
try {
const provider = w.detect();
if (provider) available.push({ ...w, provider });
} catch(e) {}
}
// Fallback: check generic window.solana if no specific wallet matched
if (available.length === 0 && window.solana) {
available.push({ name: 'SOLANA WALLET', icon: '◈', provider: window.solana, url: '#' });
}
return available;
}
function initWallet() {
const btn = $('#wallet-btn');
if (!btn) return;
btn.addEventListener('click', toggleWallet);
createWalletModal();
}
function createWalletModal() {
const modal = document.createElement('div');
modal.id = 'wallet-modal';
modal.className = 'sol-modal hidden';
modal.innerHTML = `
<div class="sol-modal-backdrop"></div>
<div class="sol-modal-content">
<div class="sol-modal-header">
<span class="sol-modal-title">SELECT WALLET</span>
<button class="sol-modal-close" id="modal-close"></button>
</div>
<div class="sol-modal-body" id="wallet-list"></div>
<div class="sol-modal-footer">
<span class="sol-modal-hint">No wallet? Install one to connect to Solana</span>
</div>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('.sol-modal-backdrop').addEventListener('click', closeModal);
modal.querySelector('#modal-close').addEventListener('click', closeModal);
}
function openModal() {
const modal = $('#wallet-modal');
const list = $('#wallet-list');
const available = getAvailableWallets();
let html = '';
if (available.length > 0) {
html += available.map(w => `
<button class="sol-wallet-option" data-wallet="${w.name}">
<span class="sol-wallet-option-icon">${w.icon}</span>
<span class="sol-wallet-option-name">${w.name}</span>
<span class="sol-wallet-option-status">DETECTED</span>
</button>
`).join('');
}
// Show install links for wallets not detected
const notInstalled = WALLETS.filter(w => { try { return !w.detect(); } catch(e) { return true; } });
if (notInstalled.length > 0) {
html += '<div class="sol-wallet-divider">NOT INSTALLED</div>';
html += notInstalled.map(w => `
<a href="${w.url}" target="_blank" rel="noopener" class="sol-wallet-option not-installed">
<span class="sol-wallet-option-icon">${w.icon}</span>
<span class="sol-wallet-option-name">${w.name}</span>
<span class="sol-wallet-option-status">INSTALL </span>
</a>
`).join('');
}
list.innerHTML = html;
// Bind click handlers for detected wallets
list.querySelectorAll('.sol-wallet-option:not(.not-installed)').forEach(btn => {
btn.addEventListener('click', () => {
const name = btn.dataset.wallet;
const wallet = available.find(w => w.name === name);
if (wallet) connectWallet(wallet);
});
});
modal.classList.remove('hidden');
}
function closeModal() {
const modal = $('#wallet-modal');
if (modal) modal.classList.add('hidden');
}
async function toggleWallet() {
@ -255,26 +354,29 @@ async function toggleWallet() {
disconnectWallet();
return;
}
// Check for Phantom
if (!window.solana || !window.solana.isPhantom) {
const bar = $('#wallet-status');
if (bar) bar.innerHTML = '<span style="color:rgba(255,50,50,0.8)">PHANTOM WALLET NOT DETECTED — <a href="https://phantom.app" target="_blank" style="color:#a855f7">INSTALL</a></span>';
return;
openModal();
}
async function connectWallet(wallet) {
try {
const resp = await window.solana.connect();
closeModal();
const resp = await wallet.provider.connect();
walletAddress = resp.publicKey.toString();
connectedProvider = wallet;
updateWalletUI();
} catch(err) {
console.error('Wallet connection failed:', err);
const status = $('#wallet-status');
if (status) status.innerHTML = `<span style="color:rgba(255,50,50,0.8)">CONNECTION REJECTED</span>`;
}
}
function disconnectWallet() {
if (window.solana) window.solana.disconnect();
if (connectedProvider && connectedProvider.provider) {
try { connectedProvider.provider.disconnect(); } catch(e) {}
}
walletAddress = null;
connectedProvider = null;
updateWalletUI();
}
@ -283,8 +385,10 @@ function updateWalletUI() {
const btn = $('#wallet-btn');
if (walletAddress) {
const wName = connectedProvider ? connectedProvider.name : 'WALLET';
const wIcon = connectedProvider ? connectedProvider.icon : '◈';
status.className = 'sol-wallet-status connected';
status.innerHTML = `● CONNECTED <span class="sol-wallet-address">${truncAddr(walletAddress)}</span>`;
status.innerHTML = `● CONNECTED VIA ${wIcon} ${wName} <span class="sol-wallet-address">${truncAddr(walletAddress)}</span>`;
btn.className = 'sol-wallet-btn disconnect';
btn.textContent = 'DISCONNECT';
} else {
@ -292,7 +396,6 @@ function updateWalletUI() {
status.innerHTML = '○ NOT CONNECTED';
btn.className = 'sol-wallet-btn';
btn.textContent = 'CONNECT WALLET';
// Clear my domains
const panel = $('#mydomains-content');
if (panel) panel.innerHTML = `<div class="sol-empty">CONNECT WALLET TO VIEW YOUR DOMAINS</div>`;
}