/* .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 = `
${favourite ? `
OWNER'S FAVOURITE
${esc(favourite)}.sol
` : ''}
`;
}
function showAvailableDomain(domain) {
const results = $('#search-results');
const price = getEstimatedPrice(domain);
results.innerHTML = `
LENGTH
${domain.length} character${domain.length !== 1 ? 's' : ''}
ESTIMATED COST
${price.amount.replace('~','')}
${price.currency}
(estimated)
REGISTRATION
Register directly from your wallet
REGISTER ${esc(domain.toUpperCase())}.SOL
`;
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 ─────────────────────────────────────────────────
let connectedProvider = null;
// ── 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 });
}
// ── 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();
}
function createWalletModal() {
const modal = document.createElement('div');
modal.id = 'wallet-modal';
modal.className = 'sol-modal hidden';
modal.innerHTML = `
`;
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');
// Small delay to let async provider injection complete
setTimeout(() => {
const available = getAvailableWallets();
const detectedNames = new Set(available.map(w => w.name));
let html = '';
if (available.length > 0) {
html += 'DETECTED
';
html += available.map(w => `
${w.icon}
${w.name}
DETECTED
`).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', (e) => {
e.preventDefault();
e.stopPropagation();
const name = btn.dataset.wallet;
const wallet = available.find(w => w.name === name);
if (wallet) connectWallet(wallet);
});
});
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();
}
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) {}
}
walletAddress = null;
connectedProvider = null;
updateWalletUI();
}
function updateWalletUI() {
const status = $('#wallet-status');
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 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 = `
`;
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 ``;
}
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 = `
`;
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.
CANCEL
CONFIRM & REGISTER
`;
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 = `
${extra || ''}
`;
}
async function executeRegistration(domain) {
if (!walletAddress || !connectedProvider) {
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 (connectedProvider.isWalletStandard) {
signature = await signAndSendWalletStandard(transaction, isVersioned, connection);
} else {
signature = await signAndSendLegacy(transaction, isVersioned, connection);
}
updateRegistrationStatus('pending', 'TRANSACTION SUBMITTED — CONFIRMING...',
``);
// 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!`,
`
CLOSE
`);
} 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)}
RETRY
CLOSE
`);
// 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 = connectedProvider.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 = connectedProvider.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';
}