/* .SOL DOMAINS — Solana Name Service Lookup & Registration */ const SNS_API = 'https://sns-sdk-proxy.bonfida.workers.dev'; const SNS_REG = 'https://www.sns.id/domain/'; const SNS_REGISTER_API = 'https://sdk-proxy.sns.id'; const SOLSCAN = 'https://solscan.io/account/'; const SOLSCAN_TX = 'https://solscan.io/tx/'; const REFERRAL_WALLET = '9NuiHh5wgRPx69BFGP1ZR8kHiBENGoJrXs5GpZzKAyn8'; const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; const SOLANA_RPC = 'https://api.mainnet-beta.solana.com'; let walletAddress = null; let currentTab = 'search'; let pendingRegistrationDomain = null; // ─── DOM ──────────────────────────────────────────────────── const $ = s => document.querySelector(s); const $$ = s => document.querySelectorAll(s); document.addEventListener('DOMContentLoaded', () => { initTabs(); initSearch(); initWallet(); createRegistrationModal(); }); // ─── TABS ─────────────────────────────────────────────────── function initTabs() { $$('.sol-tab').forEach(tab => { tab.addEventListener('click', () => { const t = tab.dataset.tab; switchTab(t); }); }); } function switchTab(t) { currentTab = t; $$('.sol-tab').forEach(tab => tab.classList.toggle('active', tab.dataset.tab === t)); $$('.sol-panel').forEach(p => p.classList.toggle('hidden', p.id !== `panel-${t}`)); if (t === 'mydomains' && walletAddress) loadMyDomains(); } // ─── SEARCH / LOOKUP ──────────────────────────────────────── function initSearch() { const input = $('#sol-search'); const btn = $('#sol-search-go'); if (!input || !btn) return; btn.addEventListener('click', () => doSearch()); input.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); }); const revInput = $('#sol-reverse'); const revBtn = $('#sol-reverse-go'); if (revInput && revBtn) { revBtn.addEventListener('click', () => doReverse()); revInput.addEventListener('keydown', e => { if (e.key === 'Enter') doReverse(); }); } } async function doSearch() { const raw = $('#sol-search').value.trim().toLowerCase(); if (!raw) return; const domain = raw.replace(/\.sol$/i, ''); const results = $('#search-results'); results.innerHTML = loadingHTML('RESOLVING DOMAIN...'); try { const res = await fetch(`${SNS_API}/resolve/${domain}`); const data = await res.json(); if (data.s === 'ok' && data.result) { await showTakenDomain(domain, data.result); } else { showAvailableDomain(domain); } } catch (err) { results.innerHTML = errorHTML(`Network error: ${err.message}`); } } async function showTakenDomain(domain, owner) { const results = $('#search-results'); let favourite = null; try { const favRes = await fetch(`${SNS_API}/favourite-domain/${owner}`); const favData = await favRes.json(); if (favData.s === 'ok') favourite = favData.result || null; } catch(e) {} results.innerHTML = `
${esc(domain)}.sol
REGISTERED
${favourite ? `
OWNER'S FAVOURITE
${esc(favourite)}.sol
` : ''}
`; } function showAvailableDomain(domain) { const results = $('#search-results'); const price = getEstimatedPrice(domain); results.innerHTML = `
${esc(domain)}.sol
AVAILABLE
LENGTH
${domain.length} character${domain.length !== 1 ? 's' : ''}
ESTIMATED COST
${price.amount.replace('~','')} ${price.currency} (estimated)
REGISTRATION
Register directly from your wallet
PAYMENT
USDC on Solana
`; const regBtn = results.querySelector('#register-domain-btn'); if (regBtn) { regBtn.addEventListener('click', () => initiateRegistration(domain)); } } // ─── REVERSE LOOKUP ───────────────────────────────────────── async function doReverse() { const addr = $('#sol-reverse').value.trim(); if (!addr) return; const results = $('#reverse-results'); results.innerHTML = loadingHTML('SCANNING WALLET...'); try { const res = await fetch(`${SNS_API}/domains/${addr}`); const data = await res.json(); // Normalise response to array — API may return various formats let domains = []; if (Array.isArray(data)) { domains = data; } else if (data && typeof data === 'object') { if (Array.isArray(data.result)) { domains = data.result; } else if (typeof data.result === 'string') { domains = [data.result]; } else if (data.result && typeof data.result === 'object' && !Array.isArray(data.result)) { // Single domain object domains = [data.result]; } } if (!domains || domains.length === 0) { results.innerHTML = `
NO .SOL DOMAINS FOUND FOR THIS WALLET
`; return; } let favourite = null; try { const favRes = await fetch(`${SNS_API}/favourite-domain/${addr}`); const favData = await favRes.json(); if (favData.s === 'ok') favourite = favData.result || null; } catch(e) {} results.innerHTML = `
DOMAINS: ${domains.length}
${favourite ? `
FAVOURITE: ${favourite}.sol
` : ''}
WALLET: ${truncAddr(addr)}
${domains.map(d => { const name = typeof d === 'string' ? d : (d.domain || d.name || String(d)); const isFav = favourite && name === favourite; return `
${esc(name)}.sol
${isFav ? '
★ FAVOURITE
' : ''}
`; }).join('')}
`; } catch(err) { results.innerHTML = errorHTML(`Network error: ${err.message}`); } } // ─── WALLET (uses global window.solWallet from wallet-connect.js) ──────────── // 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; } } 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() { const modal = document.createElement('div'); modal.id = 'wallet-modal'; modal.className = 'sol-modal hidden'; modal.innerHTML = `
SELECT WALLET
`; document.body.appendChild(modal); modal.querySelector('.sol-modal-backdrop').addEventListener('click', closeModal); modal.querySelector('#modal-close').addEventListener('click', closeModal); } 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 = sw.getAvailableWallets(); const detectedNames = new Set(available.map(w => w.name)); let html = ''; if (available.length > 0) { html += '
DETECTED
'; html += available.map(w => ` `).join(''); } const notInstalled = KNOWN_WALLETS.filter(w => !detectedNames.has(w.name)); if (notInstalled.length > 0) { html += '
NOT INSTALLED
'; html += notInstalled.map(w => ` ${w.icon} ${w.name} INSTALL ↗ `).join(''); } if (available.length === 0) { html = `
NO SOLANA WALLETS DETECTED

Install a Solana wallet extension to connect.
If you just installed one, refresh the page.
`; } list.innerHTML = html; list.querySelectorAll('.sol-wallet-option.detected').forEach(btn => { btn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const name = btn.dataset.wallet; const wallet = available.find(w => w.name === name); 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`; } } }); }); modal.classList.remove('hidden'); }, 150); } function closeModal() { const modal = $('#wallet-modal'); if (modal) modal.classList.add('hidden'); } async function toggleWallet() { if (walletAddress) { disconnectWallet(); return; } openModal(); } function disconnectWallet() { const sw = window.solWallet; if (sw) sw.disconnect(); walletAddress = null; updateWalletUI(); } function updateWalletUI() { const status = $('#wallet-status'); const btn = $('#wallet-btn'); const sw = window.solWallet; if (walletAddress) { 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'; btn.textContent = 'DISCONNECT'; } else { status.className = 'sol-wallet-status'; status.innerHTML = '○ NOT CONNECTED'; btn.className = 'sol-wallet-btn'; btn.textContent = 'CONNECT WALLET'; const panel = $('#mydomains-content'); if (panel) panel.innerHTML = `
CONNECT WALLET TO VIEW YOUR DOMAINS
`; } } // ─── MY DOMAINS ───────────────────────────────────────────── async function loadMyDomains() { if (!walletAddress) return; const content = $('#mydomains-content'); content.innerHTML = loadingHTML('LOADING YOUR DOMAINS...'); try { const res = await fetch(`${SNS_API}/domains/${walletAddress}`); const data = await res.json(); // Normalise to array let domains = []; if (Array.isArray(data)) { domains = data; } else if (data && typeof data === 'object') { if (Array.isArray(data.result)) { domains = data.result; } else if (typeof data.result === 'string') { domains = [data.result]; } else if (data.result && typeof data.result === 'object') { domains = [data.result]; } } let favourite = null; try { const favRes = await fetch(`${SNS_API}/favourite-domain/${walletAddress}`); const favData = await favRes.json(); if (favData.s === 'ok') favourite = favData.result || null; } catch(e) {} if (!domains || domains.length === 0) { const regUrl = `${SNS_REG}?ref=${REFERRAL_WALLET}`; content.innerHTML = `
NO .SOL DOMAINS FOUND
Register one on SNS.id ↗
`; return; } content.innerHTML = `
YOUR DOMAINS: ${domains.length}
${favourite ? `
FAVOURITE: ${favourite}.sol
` : ''}
${domains.map(d => { const name = typeof d === 'string' ? d : (d.domain || d.name || String(d)); const isFav = favourite && name === favourite; return `
${esc(name)}.sol
${isFav ? '
★ FAVOURITE
' : ''}
`; }).join('')}
`; } catch(err) { content.innerHTML = errorHTML(`Network error: ${err.message}`); } } // ─── HELPERS ──────────────────────────────────────────────── function truncAddr(a) { if (!a || a.length < 12) return a; return a.slice(0, 6) + '...' + a.slice(-4); } function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function loadingHTML(msg) { return `
${msg}
`; } function errorHTML(msg) { return `
ERROR // ${msg}
`; } // ─── REGISTRATION ─────────────────────────────────────────── function getEstimatedPrice(domain) { const len = domain.length; if (len <= 1) return { amount: '~750', currency: 'USDC', numeric: 750 }; if (len === 2) return { amount: '~700', currency: 'USDC', numeric: 700 }; if (len === 3) return { amount: '~640', currency: 'USDC', numeric: 640 }; if (len === 4) return { amount: '~160', currency: 'USDC', numeric: 160 }; return { amount: '~20', currency: 'USDC', numeric: 20 }; } function createRegistrationModal() { const modal = document.createElement('div'); modal.id = 'registration-modal'; modal.className = 'sol-modal hidden'; modal.innerHTML = `
CONFIRM REGISTRATION
`; document.body.appendChild(modal); modal.querySelector('.sol-modal-backdrop').addEventListener('click', closeRegistrationModal); modal.querySelector('#reg-modal-close').addEventListener('click', closeRegistrationModal); } function initiateRegistration(domain) { if (!walletAddress) { pendingRegistrationDomain = domain; openModal(); return; } showRegistrationModal(domain); } function checkPendingRegistration() { if (pendingRegistrationDomain && walletAddress) { const domain = pendingRegistrationDomain; pendingRegistrationDomain = null; setTimeout(() => showRegistrationModal(domain), 300); } } function showRegistrationModal(domain) { const modal = $('#registration-modal'); const body = $('#reg-modal-body'); const price = getEstimatedPrice(domain); body.innerHTML = `
${esc(domain)}.sol
DOMAIN ${esc(domain)}.sol
ESTIMATED COST ${price.amount} ${price.currency}
PAYMENT TOKEN USDC (Solana)
BUYER WALLET ${truncAddr(walletAddress)}
STORAGE SPACE 0 kB (minimum)
⚠ THIS IS A REAL ON-CHAIN TRANSACTION. USDC WILL BE DEDUCTED FROM YOUR WALLET. THE EXACT AMOUNT IS DETERMINED BY THE BONFIDA SNS SMART CONTRACT.
`; body.querySelector('#reg-cancel').addEventListener('click', closeRegistrationModal); body.querySelector('#reg-confirm').addEventListener('click', () => executeRegistration(domain)); modal.classList.remove('hidden'); } function closeRegistrationModal() { const modal = $('#registration-modal'); if (modal) modal.classList.add('hidden'); } function updateRegistrationStatus(state, message, extra) { const statusEl = $('#reg-status'); if (!statusEl) return; statusEl.classList.remove('hidden'); const actions = $('#reg-actions'); const warning = $('#reg-warning'); let icon, stateClass; switch (state) { case 'processing': icon = '
'; stateClass = 'processing'; if (actions) actions.style.display = 'none'; if (warning) warning.style.display = 'none'; break; case 'pending': icon = '
'; stateClass = 'pending'; break; case 'success': icon = ''; stateClass = 'success'; if (actions) actions.style.display = 'none'; if (warning) warning.style.display = 'none'; break; case 'error': icon = ''; stateClass = 'error'; if (actions) { actions.style.display = 'flex'; } if (warning) { warning.style.display = 'block'; } break; } statusEl.innerHTML = `
${icon} ${message}
${extra || ''}
`; } async function executeRegistration(domain) { if (!walletAddress || !window.solWallet?.provider) { updateRegistrationStatus('error', 'WALLET NOT CONNECTED', '

Please connect your wallet and try again.

'); return; } updateRegistrationStatus('processing', 'FETCHING TRANSACTION FROM SNS...'); try { // 1. Fetch serialised transaction from SDK proxy const params = new URLSearchParams({ buyer: walletAddress, domain: domain, space: '0', serialize: 'true', refKey: REFERRAL_WALLET, mint: USDC_MINT }); const apiUrl = `${SNS_REGISTER_API}/register?${params.toString()}`; const res = await fetch(apiUrl); if (!res.ok) { throw new Error(`SNS API returned HTTP ${res.status}`); } const data = await res.json(); if (data.s !== 'ok' || !data.result) { const errMsg = typeof data.result === 'string' ? data.result : (data.error || 'Unknown error from SNS API'); throw new Error(errMsg); } updateRegistrationStatus('processing', 'PREPARING TRANSACTION...'); // 2. Decode base64 transaction const txBytes = base64ToUint8Array(data.result); // 3. Deserialise — try VersionedTransaction first, fallback to legacy let transaction; let isVersioned = false; try { transaction = solanaWeb3.VersionedTransaction.deserialize(txBytes); isVersioned = true; } catch (e) { try { transaction = solanaWeb3.Transaction.from(txBytes); isVersioned = false; } catch (e2) { throw new Error('Failed to deserialise transaction from SNS API'); } } // 4. Get fresh blockhash from Solana RPC const connection = new solanaWeb3.Connection(SOLANA_RPC, 'confirmed'); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); // 5. Set fresh blockhash on transaction if (isVersioned) { transaction.message.recentBlockhash = blockhash; } else { transaction.recentBlockhash = blockhash; } updateRegistrationStatus('processing', 'AWAITING WALLET SIGNATURE...'); // 6. Sign and send via connected wallet let signature; if (window.solWallet && window.solWallet.isWalletStandard) { signature = await signAndSendWalletStandard(transaction, isVersioned, connection); } else { signature = await signAndSendLegacy(transaction, isVersioned, connection); } updateRegistrationStatus('pending', 'TRANSACTION SUBMITTED — CONFIRMING...', `
TX SIGNATURE ${truncAddr(signature)} ↗
`); // 7. Confirm transaction on-chain const confirmation = await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed'); if (confirmation.value.err) { throw new Error('Transaction failed on-chain: ' + JSON.stringify(confirmation.value.err)); } // 8. Success! updateRegistrationStatus('success', `${domain.toUpperCase()}.SOL REGISTERED SUCCESSFULLY!`, `
TRANSACTION View on Solscan ↗
DOMAIN View on SNS.id ↗
`); } catch (err) { console.error('Registration error:', err); let userMessage = err.message || 'Unknown error'; // User-friendly error messages if (/reject|denied|cancel|declined|disapproved/i.test(userMessage)) { userMessage = 'Transaction was rejected by your wallet.'; } else if (/insufficient|not enough|0x1/i.test(userMessage)) { userMessage = 'Insufficient USDC balance. Ensure you have enough USDC to cover the registration cost plus network fees.'; } else if (/already taken|already registered|already exists|registered/i.test(userMessage)) { userMessage = 'This domain was just registered by someone else. Try a different name.'; } else if (/blockhash|expired|block height exceeded/i.test(userMessage)) { userMessage = 'Transaction expired. Please try again.'; } else if (/network|fetch|failed to fetch|CORS/i.test(userMessage)) { userMessage = 'Network error. Check your connection and try again.'; } updateRegistrationStatus('error', 'REGISTRATION FAILED', `

${esc(userMessage)}

`); // Bind retry after DOM update const retryBtn = document.querySelector('#reg-retry-btn'); if (retryBtn) retryBtn.addEventListener('click', () => executeRegistration(domain)); } } async function signAndSendLegacy(transaction, isVersioned, connection) { const provider = window.solWallet.provider; // Try signAndSendTransaction first (Phantom, Solflare support this) if (typeof provider.signAndSendTransaction === 'function') { try { const result = await provider.signAndSendTransaction(transaction, { skipPreflight: false, preflightCommitment: 'confirmed' }); return result.signature || result; } catch (e) { if (/reject|denied|cancel|declined|disapproved/i.test(e.message || '')) throw e; console.warn('signAndSendTransaction failed, falling back to signTransaction:', e.message); } } // Fallback: signTransaction + manual send if (typeof provider.signTransaction !== 'function') { throw new Error('Wallet does not support transaction signing.'); } const signed = await provider.signTransaction(transaction); const rawTx = signed.serialize(); const signature = await connection.sendRawTransaction(rawTx, { skipPreflight: false, preflightCommitment: 'confirmed' }); return signature; } async function signAndSendWalletStandard(transaction, isVersioned, connection) { const wallet = window.solWallet.provider; const account = wallet.accounts?.[0]; if (!account) throw new Error('No wallet account available.'); // Serialise the transaction for Wallet Standard let txBytes; if (isVersioned) { txBytes = transaction.serialize(); } else { txBytes = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }); } // Try signAndSendTransaction first const signAndSendFeature = wallet.features?.['solana:signAndSendTransaction']; if (signAndSendFeature) { try { const results = await signAndSendFeature.signAndSendTransaction({ account, transaction: txBytes, chain: 'solana:mainnet' }); const result = Array.isArray(results) ? results[0] : results; if (result.signature) { if (typeof result.signature === 'string') return result.signature; return uint8ArrayToBase58(new Uint8Array(result.signature)); } return result; } catch (e) { if (/reject|denied|cancel|declined|disapproved/i.test(e.message || '')) throw e; console.warn('WS signAndSendTransaction failed, falling back:', e.message); } } // Fallback: signTransaction + manual send const signFeature = wallet.features?.['solana:signTransaction']; if (!signFeature) throw new Error('Wallet does not support Solana transaction signing.'); const results = await signFeature.signTransaction({ account, transaction: txBytes, chain: 'solana:mainnet' }); const result = Array.isArray(results) ? results[0] : results; const signedBytes = result.signedTransaction; const signature = await connection.sendRawTransaction(signedBytes, { skipPreflight: false, preflightCommitment: 'confirmed' }); return signature; } function base64ToUint8Array(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } function uint8ArrayToBase58(bytes) { const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; let result = ''; let num = BigInt(0); for (const b of bytes) num = num * 256n + BigInt(b); while (num > 0n) { result = ALPHABET[Number(num % 58n)] + result; num = num / 58n; } for (const b of bytes) { if (b === 0) result = '1' + result; else break; } return result || '1'; }