378 lines
14 KiB
JavaScript
378 lines
14 KiB
JavaScript
/* .SOL DOMAINS — Solana Name Service Lookup & Registration */
|
|
|
|
const SNS_API = 'https://sns-sdk-proxy.bonfida.workers.dev';
|
|
const BONFIDA_REG = 'https://naming.bonfida.org/#/domain/';
|
|
const SOLSCAN = 'https://solscan.io/account/';
|
|
|
|
let walletAddress = null;
|
|
let currentTab = 'search';
|
|
|
|
// ─── DOM ────────────────────────────────────────────────────
|
|
const $ = s => document.querySelector(s);
|
|
const $$ = s => document.querySelectorAll(s);
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initTabs();
|
|
initSearch();
|
|
initWallet();
|
|
});
|
|
|
|
// ─── 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(); });
|
|
|
|
// Reverse lookup
|
|
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;
|
|
|
|
// Strip .sol if user typed it
|
|
const domain = raw.replace(/\.sol$/i, '');
|
|
const results = $('#search-results');
|
|
results.innerHTML = loadingHTML('RESOLVING DOMAIN...');
|
|
|
|
try {
|
|
// Try to resolve — if it resolves, domain is taken
|
|
const res = await fetch(`${SNS_API}/resolve/${domain}`);
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const owner = data.result || data;
|
|
// Domain is taken — show info
|
|
await showTakenDomain(domain, typeof owner === 'string' ? owner : owner.result || JSON.stringify(owner));
|
|
} else if (res.status === 404) {
|
|
// Domain is available
|
|
showAvailableDomain(domain);
|
|
} else {
|
|
results.innerHTML = errorHTML('Failed to query domain. Try again.');
|
|
}
|
|
} catch (err) {
|
|
results.innerHTML = errorHTML(`Network error: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
async function showTakenDomain(domain, owner) {
|
|
const results = $('#search-results');
|
|
|
|
// Try to get more data
|
|
let records = {};
|
|
try {
|
|
const recRes = await fetch(`${SNS_API}/record-v2/${domain}/SOL`);
|
|
if (recRes.ok) {
|
|
const recData = await recRes.json();
|
|
records.sol = recData.result || null;
|
|
}
|
|
} catch(e) {}
|
|
|
|
// Try favourite domain of owner
|
|
let favourite = null;
|
|
try {
|
|
const favRes = await fetch(`${SNS_API}/favourite-domain/${owner}`);
|
|
if (favRes.ok) {
|
|
const favData = await favRes.json();
|
|
favourite = favData.result || null;
|
|
}
|
|
} catch(e) {}
|
|
|
|
results.innerHTML = `
|
|
<div class="sol-result-card">
|
|
<div class="sol-result-header">
|
|
<div class="sol-result-domain">${esc(domain)}<span class="sol-ext">.sol</span></div>
|
|
<div class="sol-result-status taken">REGISTERED</div>
|
|
</div>
|
|
<div class="sol-result-body">
|
|
<div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">OWNER</span>
|
|
<div class="sol-result-value">
|
|
<a href="${SOLSCAN}${esc(owner)}" target="_blank" rel="noopener">${truncAddr(owner)}</a>
|
|
</div>
|
|
</div>
|
|
${favourite ? `
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">OWNER'S FAVOURITE</span>
|
|
<div class="sol-result-value">${esc(favourite)}.sol</div>
|
|
</div>` : ''}
|
|
</div>
|
|
<div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">SOLSCAN</span>
|
|
<div class="sol-result-value">
|
|
<a href="${SOLSCAN}${esc(owner)}" target="_blank" rel="noopener">View on Solscan ↗</a>
|
|
</div>
|
|
</div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">BONFIDA</span>
|
|
<div class="sol-result-value">
|
|
<a href="${BONFIDA_REG}${esc(domain)}" target="_blank" rel="noopener">View on Bonfida ↗</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function showAvailableDomain(domain) {
|
|
const results = $('#search-results');
|
|
const len = domain.length;
|
|
let priceEstimate = '';
|
|
if (len <= 1) priceEstimate = '~750 USDC';
|
|
else if (len === 2) priceEstimate = '~700 USDC';
|
|
else if (len === 3) priceEstimate = '~640 USDC';
|
|
else if (len === 4) priceEstimate = '~160 USDC';
|
|
else priceEstimate = '~20 USDC';
|
|
|
|
results.innerHTML = `
|
|
<div class="sol-result-card">
|
|
<div class="sol-result-header">
|
|
<div class="sol-result-domain">${esc(domain)}<span class="sol-ext">.sol</span></div>
|
|
<div class="sol-result-status available">AVAILABLE</div>
|
|
</div>
|
|
<div class="sol-result-body">
|
|
<div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">LENGTH</span>
|
|
<div class="sol-result-value">${len} character${len !== 1 ? 's' : ''}</div>
|
|
</div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">ESTIMATED COST</span>
|
|
<div class="sol-price">
|
|
<span class="sol-price-amount">${priceEstimate.split(' ')[0].replace('~','')}</span>
|
|
<span class="sol-price-currency">${priceEstimate.split(' ')[1] || 'USDC'}</span>
|
|
<span class="sol-price-usd">(estimated)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="sol-result-field">
|
|
<span class="sol-result-label">REGISTRATION</span>
|
|
<div class="sol-result-value">Register via Bonfida's official portal</div>
|
|
</div>
|
|
<a href="${BONFIDA_REG}${esc(domain)}" target="_blank" rel="noopener" class="sol-register-btn">REGISTER ${esc(domain.toUpperCase())}.SOL ↗</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// ─── REVERSE LOOKUP ─────────────────────────────────────────
|
|
async function doReverse() {
|
|
const addr = $('#sol-reverse').value.trim();
|
|
if (!addr) return;
|
|
|
|
const results = $('#reverse-results');
|
|
results.innerHTML = loadingHTML('SCANNING WALLET...');
|
|
|
|
try {
|
|
// Get all domains for this wallet
|
|
const res = await fetch(`${SNS_API}/domains/${addr}`);
|
|
if (!res.ok) {
|
|
results.innerHTML = errorHTML('Could not resolve wallet. Check the address.');
|
|
return;
|
|
}
|
|
const data = await res.json();
|
|
const domains = data.result || data;
|
|
|
|
if (!domains || !domains.length) {
|
|
results.innerHTML = `<div class="sol-empty">NO .SOL DOMAINS FOUND FOR THIS WALLET</div>`;
|
|
return;
|
|
}
|
|
|
|
// Get favourite
|
|
let favourite = null;
|
|
try {
|
|
const favRes = await fetch(`${SNS_API}/favourite-domain/${addr}`);
|
|
if (favRes.ok) {
|
|
const favData = await favRes.json();
|
|
favourite = favData.result || null;
|
|
}
|
|
} catch(e) {}
|
|
|
|
results.innerHTML = `
|
|
<div class="sol-stats-bar">
|
|
<div class="sol-stat">DOMAINS: <span class="sol-stat-value">${domains.length}</span></div>
|
|
${favourite ? `<div class="sol-stat">FAVOURITE: <span class="sol-stat-value">${favourite}.sol</span></div>` : ''}
|
|
<div class="sol-stat">WALLET: <span class="sol-stat-value">${truncAddr(addr)}</span></div>
|
|
</div>
|
|
<div class="sol-domains-grid">
|
|
${domains.map(d => {
|
|
const name = typeof d === 'string' ? d : (d.domain || d);
|
|
const isFav = favourite && name === favourite;
|
|
return `
|
|
<div class="sol-domain-card">
|
|
<div class="sol-domain-name">${esc(name)}<span class="sol-ext">.sol</span></div>
|
|
${isFav ? '<div class="sol-domain-fav">★ FAVOURITE</div>' : ''}
|
|
</div>
|
|
`;
|
|
}).join('')}
|
|
</div>
|
|
`;
|
|
} catch(err) {
|
|
results.innerHTML = errorHTML(`Network error: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
// ─── WALLET ─────────────────────────────────────────────────
|
|
function initWallet() {
|
|
const btn = $('#wallet-btn');
|
|
if (!btn) return;
|
|
btn.addEventListener('click', toggleWallet);
|
|
}
|
|
|
|
async function toggleWallet() {
|
|
if (walletAddress) {
|
|
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;
|
|
}
|
|
|
|
try {
|
|
const resp = await window.solana.connect();
|
|
walletAddress = resp.publicKey.toString();
|
|
updateWalletUI();
|
|
} catch(err) {
|
|
console.error('Wallet connection failed:', err);
|
|
}
|
|
}
|
|
|
|
function disconnectWallet() {
|
|
if (window.solana) window.solana.disconnect();
|
|
walletAddress = null;
|
|
updateWalletUI();
|
|
}
|
|
|
|
function updateWalletUI() {
|
|
const status = $('#wallet-status');
|
|
const btn = $('#wallet-btn');
|
|
|
|
if (walletAddress) {
|
|
status.className = 'sol-wallet-status connected';
|
|
status.innerHTML = `● CONNECTED <span class="sol-wallet-address">${truncAddr(walletAddress)}</span>`;
|
|
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';
|
|
// Clear my domains
|
|
const panel = $('#mydomains-content');
|
|
if (panel) panel.innerHTML = `<div class="sol-empty">CONNECT WALLET TO VIEW YOUR DOMAINS</div>`;
|
|
}
|
|
}
|
|
|
|
// ─── 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}`);
|
|
if (!res.ok) {
|
|
content.innerHTML = errorHTML('Failed to load domains.');
|
|
return;
|
|
}
|
|
|
|
const data = await res.json();
|
|
const domains = data.result || data;
|
|
|
|
// Get favourite
|
|
let favourite = null;
|
|
try {
|
|
const favRes = await fetch(`${SNS_API}/favourite-domain/${walletAddress}`);
|
|
if (favRes.ok) {
|
|
const favData = await favRes.json();
|
|
favourite = favData.result || null;
|
|
}
|
|
} catch(e) {}
|
|
|
|
if (!domains || !domains.length) {
|
|
content.innerHTML = `
|
|
<div class="sol-empty">
|
|
NO .SOL DOMAINS FOUND<br>
|
|
<a href="${BONFIDA_REG}" target="_blank" rel="noopener" style="color:#a855f7; font-size:0.6rem">Register one on Bonfida ↗</a>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
content.innerHTML = `
|
|
<div class="sol-stats-bar">
|
|
<div class="sol-stat">YOUR DOMAINS: <span class="sol-stat-value">${domains.length}</span></div>
|
|
${favourite ? `<div class="sol-stat">FAVOURITE: <span class="sol-stat-value">${favourite}.sol</span></div>` : ''}
|
|
</div>
|
|
<div class="sol-domains-grid">
|
|
${domains.map(d => {
|
|
const name = typeof d === 'string' ? d : (d.domain || d);
|
|
const isFav = favourite && name === favourite;
|
|
return `
|
|
<div class="sol-domain-card">
|
|
<div class="sol-domain-name">${esc(name)}<span class="sol-ext">.sol</span></div>
|
|
${isFav ? '<div class="sol-domain-fav">★ FAVOURITE</div>' : ''}
|
|
</div>
|
|
`;
|
|
}).join('')}
|
|
</div>
|
|
`;
|
|
} 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 `<div class="sol-loading"><div class="sol-loading-spinner"></div><div class="sol-loading-text">${msg}</div></div>`;
|
|
}
|
|
|
|
function errorHTML(msg) {
|
|
return `<div class="sol-error">ERROR // ${msg}</div>`;
|
|
}
|