feat: .sol domain registration app - search, reverse lookup, wallet connect
This commit is contained in:
parent
199a318d8d
commit
d0d13c3f30
3 changed files with 946 additions and 0 deletions
448
css/soldomains.css
Normal file
448
css/soldomains.css
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
/* .SOL DOMAINS - Solana Name Service */
|
||||
|
||||
.sol-container {
|
||||
max-width: clamp(1000px, 85vw, 1800px);
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem 3rem;
|
||||
}
|
||||
|
||||
/* Search Section */
|
||||
.sol-search-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sol-search-box {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
background: rgba(16, 16, 16, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid rgba(138, 43, 226, 0.6);
|
||||
padding: 1rem 1.25rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sol-search-box:focus-within {
|
||||
border-left-color: #a855f7;
|
||||
box-shadow: 0 0 20px rgba(138, 43, 226, 0.1);
|
||||
}
|
||||
|
||||
.sol-search-prefix {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: #a855f7;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sol-search-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #ffffff;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sol-search-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.sol-search-suffix {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(138, 43, 226, 0.6);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sol-search-btn {
|
||||
background: rgba(138, 43, 226, 0.15);
|
||||
border: 1px solid rgba(138, 43, 226, 0.3);
|
||||
color: #a855f7;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 2px;
|
||||
padding: 0.6rem 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.sol-search-btn:hover {
|
||||
background: rgba(138, 43, 226, 0.25);
|
||||
border-color: #a855f7;
|
||||
box-shadow: 0 0 15px rgba(138, 43, 226, 0.2);
|
||||
}
|
||||
|
||||
/* Wallet Connection */
|
||||
.sol-wallet-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
background: rgba(16, 16, 16, 0.6);
|
||||
border: 1px solid rgba(138, 43, 226, 0.15);
|
||||
}
|
||||
|
||||
.sol-wallet-status {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.65rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sol-wallet-status.connected {
|
||||
color: #00cc44;
|
||||
}
|
||||
|
||||
.sol-wallet-address {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.6rem;
|
||||
color: rgba(138, 43, 226, 0.7);
|
||||
letter-spacing: 1px;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.sol-wallet-btn {
|
||||
background: rgba(138, 43, 226, 0.12);
|
||||
border: 1px solid rgba(138, 43, 226, 0.3);
|
||||
color: #a855f7;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 2px;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.sol-wallet-btn:hover {
|
||||
background: rgba(138, 43, 226, 0.25);
|
||||
border-color: #a855f7;
|
||||
}
|
||||
|
||||
.sol-wallet-btn.disconnect {
|
||||
border-color: rgba(255, 50, 50, 0.3);
|
||||
color: rgba(255, 50, 50, 0.7);
|
||||
background: rgba(255, 50, 50, 0.08);
|
||||
}
|
||||
|
||||
.sol-wallet-btn.disconnect:hover {
|
||||
border-color: rgba(255, 50, 50, 0.6);
|
||||
background: rgba(255, 50, 50, 0.15);
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.sol-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sol-tab {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 2px;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 0.75rem 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.sol-tab:hover {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.sol-tab.active {
|
||||
color: #a855f7;
|
||||
border-bottom-color: #a855f7;
|
||||
}
|
||||
|
||||
/* Results Panel */
|
||||
.sol-results {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.sol-result-card {
|
||||
background: rgba(16, 16, 16, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid rgba(138, 43, 226, 0.4);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sol-result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sol-result-domain {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 1rem;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.sol-result-domain .sol-ext {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.sol-result-status {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 2px;
|
||||
padding: 0.3rem 0.75rem;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.sol-result-status.available {
|
||||
color: #00cc44;
|
||||
border-color: rgba(0, 204, 68, 0.3);
|
||||
background: rgba(0, 204, 68, 0.08);
|
||||
}
|
||||
|
||||
.sol-result-status.taken {
|
||||
color: rgba(255, 170, 0, 0.8);
|
||||
border-color: rgba(255, 170, 0, 0.3);
|
||||
background: rgba(255, 170, 0, 0.08);
|
||||
}
|
||||
|
||||
.sol-result-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.sol-result-body { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.sol-result-field {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sol-result-label {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.55rem;
|
||||
color: rgba(138, 43, 226, 0.6);
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sol-result-value {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.sol-result-value a {
|
||||
color: #a855f7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sol-result-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Register Button */
|
||||
.sol-register-btn {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, rgba(138, 43, 226, 0.25), rgba(168, 85, 247, 0.15));
|
||||
border: 1px solid rgba(138, 43, 226, 0.5);
|
||||
color: #ffffff;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 3px;
|
||||
padding: 0.75rem 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sol-register-btn:hover {
|
||||
background: linear-gradient(135deg, rgba(138, 43, 226, 0.4), rgba(168, 85, 247, 0.3));
|
||||
border-color: #a855f7;
|
||||
box-shadow: 0 0 25px rgba(138, 43, 226, 0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sol-register-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* My Domains List */
|
||||
.sol-domains-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) { .sol-domains-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 600px) { .sol-domains-grid { grid-template-columns: 1fr; } }
|
||||
|
||||
.sol-domain-card {
|
||||
background: rgba(16, 16, 16, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid rgba(138, 43, 226, 0.4);
|
||||
padding: 1rem 1.25rem;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.sol-domain-card:hover {
|
||||
border-left-color: #a855f7;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
}
|
||||
|
||||
.sol-domain-name {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.sol-domain-name .sol-ext {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.sol-domain-fav {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.5rem;
|
||||
color: #00cc44;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Loading / Empty States */
|
||||
.sol-loading {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.sol-loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(138, 43, 226, 0.2);
|
||||
border-top-color: #a855f7;
|
||||
border-radius: 50%;
|
||||
animation: sol-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes sol-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.sol-loading-text {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.65rem;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
letter-spacing: 2px;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.sol-empty {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.65rem;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sol-error {
|
||||
background: rgba(255, 50, 50, 0.08);
|
||||
border: 1px solid rgba(255, 50, 50, 0.2);
|
||||
border-left: 3px solid rgba(255, 50, 50, 0.5);
|
||||
padding: 1rem 1.25rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.65rem;
|
||||
color: rgba(255, 50, 50, 0.8);
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Reverse Lookup */
|
||||
.sol-reverse-box {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
background: rgba(16, 16, 16, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid rgba(138, 43, 226, 0.4);
|
||||
padding: 1rem 1.25rem;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.sol-reverse-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.sol-reverse-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Stats Bar */
|
||||
.sol-stats-bar {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 0.5rem 0;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid rgba(138, 43, 226, 0.1);
|
||||
}
|
||||
|
||||
.sol-stat {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.55rem;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sol-stat-value {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
/* Price Display */
|
||||
.sol-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.sol-price-amount {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 1.1rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.sol-price-currency {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.65rem;
|
||||
color: rgba(138, 43, 226, 0.7);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.sol-price-usd {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.6rem;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
378
js/soldomains.js
Normal file
378
js/soldomains.js
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
/* .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>`;
|
||||
}
|
||||
120
soldomains/index.html
Normal file
120
soldomains/index.html
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JAESWIFT // .SOL DOMAINS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/section.css">
|
||||
<link rel="stylesheet" href="/css/soldomains.css">
|
||||
<style>
|
||||
.hidden { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="scanline-overlay"></div>
|
||||
<div class="grid-bg"></div>
|
||||
|
||||
<nav class="nav-main" id="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="/" class="nav-logo">
|
||||
<span class="logo-bracket">[</span> JAE <span class="logo-bracket">]</span>
|
||||
</a>
|
||||
<button class="nav-toggle" id="navToggle" aria-label="Menu">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<ul class="nav-menu" id="navMenu"></ul>
|
||||
<div class="nav-status">
|
||||
<span class="nav-clock" id="navClock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="/">HOME</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="/armoury">ARMOURY</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="/armoury/lab.html">LAB</a>
|
||||
<span class="separator">/</span>
|
||||
<span class="current">.SOL DOMAINS</span>
|
||||
</div>
|
||||
|
||||
<section class="section-header" style="padding-top: calc(var(--nav-height) + 1.5rem);">
|
||||
<div class="section-header-label">LAB // SOLANA NAME SERVICE</div>
|
||||
<h1 class="section-header-title">.SOL DOMAINS</h1>
|
||||
<p class="section-header-sub">> Look up, register, and manage Solana .sol domains on-chain via Bonfida SNS.</p>
|
||||
</section>
|
||||
|
||||
<div class="sol-container">
|
||||
|
||||
<!-- Wallet Bar -->
|
||||
<div class="sol-wallet-bar">
|
||||
<div class="sol-wallet-status" id="wallet-status">○ NOT CONNECTED</div>
|
||||
<button class="sol-wallet-btn" id="wallet-btn">CONNECT WALLET</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="sol-tabs">
|
||||
<button class="sol-tab active" data-tab="search">SEARCH</button>
|
||||
<button class="sol-tab" data-tab="reverse">REVERSE LOOKUP</button>
|
||||
<button class="sol-tab" data-tab="mydomains">MY DOMAINS</button>
|
||||
</div>
|
||||
|
||||
<!-- Panel: Search -->
|
||||
<div class="sol-panel" id="panel-search">
|
||||
<div class="sol-search-section">
|
||||
<div class="sol-search-box">
|
||||
<span class="sol-search-prefix">QUERY //</span>
|
||||
<input type="text" class="sol-search-input" id="sol-search" placeholder="enter domain name" autocomplete="off" spellcheck="false">
|
||||
<span class="sol-search-suffix">.sol</span>
|
||||
<button class="sol-search-btn" id="sol-search-go">RESOLVE</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sol-results" id="search-results">
|
||||
<div class="sol-empty">ENTER A .SOL DOMAIN NAME TO CHECK AVAILABILITY AND OWNERSHIP</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel: Reverse Lookup -->
|
||||
<div class="sol-panel hidden" id="panel-reverse">
|
||||
<div class="sol-search-section">
|
||||
<div class="sol-reverse-box">
|
||||
<span class="sol-search-prefix">WALLET //</span>
|
||||
<input type="text" class="sol-reverse-input" id="sol-reverse" placeholder="paste Solana wallet address" autocomplete="off" spellcheck="false">
|
||||
<button class="sol-search-btn" id="sol-reverse-go">SCAN</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sol-results" id="reverse-results">
|
||||
<div class="sol-empty">PASTE A SOLANA WALLET ADDRESS TO FIND ALL ASSOCIATED .SOL DOMAINS</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel: My Domains -->
|
||||
<div class="sol-panel hidden" id="panel-mydomains">
|
||||
<div class="sol-results" id="mydomains-content">
|
||||
<div class="sol-empty">CONNECT WALLET TO VIEW YOUR DOMAINS</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-container">
|
||||
<div class="footer-left">
|
||||
<span class="footer-logo">[JAE]</span>
|
||||
<span class="footer-copy">© 2026 JAESWIFT.XYZ</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span class="footer-signal">SIGNAL ████<span class="signal-flicker">█</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/clock.js"></script>
|
||||
<script src="/js/soldomains.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue