/* global solanaWeb3 */ (() => { const { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction } = solanaWeb3; // DOM helpers const $ = (id) => document.getElementById(id); const DEBUG = new URLSearchParams(location.search).has('debug'); const log = (m, cls='') => { if (cls === 'err') console.error(m); else if (cls === 'warn') console.warn(m); if (DEBUG) console.debug(m); }; // SNS (Solana Name Service) resolution const snsCache = new Map(); const NAME_PROGRAM_ID = new PublicKey('namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX'); async function findOwnedNameAccountsForUser(connection, userAccount) { try { const filters = [ { memcmp: { offset: 32, bytes: userAccount.toBase58(), }, }, ]; const accounts = await connection.getProgramAccounts(NAME_PROGRAM_ID, { filters, }); return accounts.map((a) => a.publicKey); } catch (e) { return []; } } async function resolveSNSAddress(address) { try { if (snsCache.has(address)) { return snsCache.get(address); } const { cluster } = getCfg(); const conn = new Connection(cluster, 'confirmed'); const publicKey = new PublicKey(address); // Find owned name accounts const nameAccounts = await findOwnedNameAccountsForUser(conn, publicKey); if (nameAccounts.length > 0) { // Get the first domain name account data const accountInfo = await conn.getAccountInfo(nameAccounts[0]); if (accountInfo && accountInfo.data) { // Parse the domain name from account data // SNS account structure: [96 bytes header] + [4 bytes name length] + [name data] const data = accountInfo.data; if (data.length > 100) { const nameLength = data.readUInt32LE(96); if (nameLength > 0 && nameLength < 64) { const domainBytes = data.slice(100, 100 + nameLength); const domain = new TextDecoder().decode(domainBytes); if (domain && domain.includes('.')) { snsCache.set(address, domain); return domain; } } } } } // If no SNS name found, cache the negative result snsCache.set(address, null); return null; } catch (e) { // Silently fail and return null snsCache.set(address, null); return null; } } async function formatAddressWithSNS(address) { const snsName = await resolveSNSAddress(address); if (snsName) { return snsName; } return `${address.slice(0,4)}…${address.slice(-4)}`; } // Providers function standardToLegacyProvider(stdWallet) { const features = stdWallet?.features || {}; const connectFeature = features['standard:connect']; const eventsFeature = features['standard:events']; const disconnectFeature = features['standard:disconnect']; const signTxFeature = features['solana:signTransactions']; const signAndSendFeature = features['solana:signAndSendTransaction']; const signMsgFeature = features['solana:signMessage']; let currentAccount = null; const listeners = { connect: [], disconnect: [] }; const emit = (evt, ...args) => (listeners[evt] || []).forEach((fn) => { try { fn(...args); } catch {} }); // Forward standard events into legacy-style events if (eventsFeature?.on) { try { eventsFeature.on('change', ({ accounts }) => { if (accounts && accounts[0]) { currentAccount = accounts[0]; provider.publicKey = { toString: () => accounts[0].address, toBase58: () => accounts[0].address }; emit('connect'); } else { currentAccount = null; provider.publicKey = null; emit('disconnect'); } }); } catch {} } const provider = { isStandard: true, publicKey: null, on: (evt, fn) => { if (!listeners[evt]) listeners[evt] = []; listeners[evt].push(fn); }, off: (evt, fn) => { listeners[evt] = (listeners[evt] || []).filter((f) => f !== fn); }, connect: async () => { if (!connectFeature?.connect) throw new Error('Wallet Standard connect not available'); const { accounts } = await connectFeature.connect(); if (!accounts?.length) throw new Error('No account returned'); currentAccount = accounts[0]; provider.publicKey = { toString: () => accounts[0].address, toBase58: () => accounts[0].address }; emit('connect'); return { publicKey: provider.publicKey }; }, disconnect: async () => { try { if (disconnectFeature?.disconnect) await disconnectFeature.disconnect(); } finally { currentAccount = null; provider.publicKey = null; emit('disconnect'); } }, signTransaction: async (tx) => { if (signTxFeature?.signTransactions) { const signed = await signTxFeature.signTransactions({ transactions: [tx] }); // Some implementations return { signedTransactions }, others return array directly const arr = Array.isArray(signed) ? signed : (signed?.signedTransactions || []); if (!arr[0]) throw new Error('signTransactions returned no result'); return arr[0]; } throw new Error('Wallet Standard wallet does not support signTransactions'); }, _standard: { signTransactions: signTxFeature?.signTransactions, signAndSendTransaction: signAndSendFeature?.signAndSendTransaction, signMessage: signMsgFeature?.signMessage } }; return provider; } function detectProviders() { const map = new Map(); const add = (id, name, provider) => { if (provider && !map.has(id)) map.set(id, { id, name, provider }); }; // Injected add('phantom','Phantom', window.solana?.isPhantom ? window.solana : null); add('solflare','Solflare', window.solflare?.isSolflare ? window.solflare : null); add('backpack','Backpack', window.backpack?.isBackpack ? window.backpack : null); add('glow','Glow', window.glow?.solana || null); add('exodus','Exodus', window.exodus?.solana || null); // Wallet Standard (if present) try { const std = window.navigator?.wallets?.get?.(); if (Array.isArray(std)) { for (const w of std) { const id = w.name?.toLowerCase?.().replace(/\s+/g,'-') || 'standard'; add(id, w.name || id, standardToLegacyProvider(w)); } } } catch {} return Array.from(map.values()); } function populateWalletSelect(){ const select = $('walletSelect'); if(!select) return; const detected = detectProviders(); // Keep first option as Auto-detect, remove the rest, then append detected select.innerHTML = '' + detected.map(d=>``).join(''); // If none detected, keep auto only } // Centralized ticker functions async function loadTickerFromServer() { try { const response = await fetch('ticker-api.php', { method: 'GET', cache: 'no-cache' }); if (!response.ok) return; const tickerData = await response.json(); const track = document.getElementById('ticker'); if (!track) return; // Clear existing ticker items track.innerHTML = ''; // Add items from server (reverse order since server stores newest first) for (const item of tickerData.reverse()) { const span = document.createElement('span'); span.className = 'ticker-item ' + (item.type || ''); // Try to resolve SNS names in existing ticker text let displayText = item.text; if (item.snsName) { // If SNS name is already stored, use it displayText = displayText.replace(/[A-Za-z0-9]{4}…[A-Za-z0-9]{4}/, item.snsName); } else { // Try to extract address and resolve SNS const addressMatch = displayText.match(/([A-Za-z0-9]{4})…([A-Za-z0-9]{4})/); if (addressMatch && item.fullAddress) { const snsName = await resolveSNSAddress(item.fullAddress); if (snsName) { displayText = displayText.replace(addressMatch[0], snsName); } } } span.textContent = displayText; track.appendChild(span); } } catch (e) { log('Failed to load ticker data from server', 'warn'); } } async function saveTickerToServer(text, type, fullAddress = null, snsName = null) { try { const payload = { text, type }; if (fullAddress) payload.fullAddress = fullAddress; if (snsName) payload.snsName = snsName; await fetch('ticker-api.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } catch (e) { log('Failed to save ticker item to server', 'warn'); } } async function pushTickerItem(text, cls, address = null) { const track = document.getElementById('ticker'); if (!track) return; let displayText = text; let snsName = null; // Try to resolve SNS name if address is provided if (address) { snsName = await resolveSNSAddress(address); if (snsName) { displayText = text.replace(/[A-Za-z0-9]{4}…[A-Za-z0-9]{4}/, snsName); } } // Add to local DOM immediately for responsiveness const span = document.createElement('span'); span.className = 'ticker-item ' + (cls || ''); span.textContent = displayText; track.appendChild(span); // Limit items to prevent DOM bloat (keep more than server since DOM updates faster) while (track.childNodes.length > 50) { track.removeChild(track.firstChild); } // Save to server for other users saveTickerToServer(displayText, cls || '', address, snsName); } // Updated discriminator functions for our new program async function getDiscriminators(){ const buyTokens = await anchorDisc('buy_tokens'); const sellTokens = await anchorDisc('sell_tokens'); return { buy: buyTokens, sell: sellTokens }; } function b64ToBytes(b64){ const bin = atob(b64); const arr = new Uint8Array(bin.length); for(let i=0;ibuf.length) return false; for(let i=0;ik.toString()); const feePayer = keys[0] || ''; const ixList = msg.instructions || msg.compiledInstructions || []; for(const ix of ixList){ const progIdx = ix.programIdIndex ?? undefined; const prog = progIdx !== undefined ? keys[progIdx] : ix.programId?.toString(); if(prog !== programId.toString()) continue; const dataB64 = ix.data || ''; const data = typeof dataB64 === 'string' ? b64ToBytes(dataB64) : new Uint8Array(); if(startsWith(data, discs.buy)){ const lamports = Number(readLeU64(data, 8)); const sol = lamports / 1_000_000_000; await pushTickerItem(`BUY ${sol.toFixed(3)} SOL • ${feePayer.slice(0,4)}…${feePayer.slice(-4)}`, 'buy', feePayer); } else if(startsWith(data, discs.sell)){ const tokensRaw = Number(readLeU64(data, 8)); const t = tokensRaw / 1_000_000_000; await pushTickerItem(`SELL ${t.toFixed(3)} tk • ${feePayer.slice(0,4)}…${feePayer.slice(-4)}`, 'sell', feePayer); } } } }catch(e){ /*silent*/ } } function pickProvider() { const sel = $('walletSelect').value; const detected = detectProviders(); if (sel !== 'auto') return detected.find(p => p.id === sel)?.provider; return detected[0]?.provider; } // Buffer helpers function le64(n) { const b=new ArrayBuffer(8); new DataView(b).setBigUint64(0, BigInt(n), true); return new Uint8Array(b); } async function anchorDisc(name){ const data=new TextEncoder().encode(`global:${name}`); const h=await crypto.subtle.digest('SHA-256', data); return new Uint8Array(h).slice(0,8); } async function buildIx(name, args){ const disc=await anchorDisc(name); const out=new Uint8Array(disc.length+args.length); out.set(disc,0); out.set(args,disc.length); return out; } // ATAs const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const ATA_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); async function findAta(mint, owner){ const [addr] = await PublicKey.findProgramAddress([ owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer() ], ATA_PROGRAM_ID); return addr; } function createAtaIx(payer, ata, owner, mint){ const keys=[ { pubkey:payer, isSigner:true, isWritable:true }, { pubkey:ata, isSigner:false, isWritable:true }, { pubkey:owner, isSigner:false, isWritable:false }, { pubkey:mint, isSigner:false, isWritable:false }, { pubkey:SystemProgram.programId, isSigner:false, isWritable:false }, { pubkey:TOKEN_PROGRAM_ID, isSigner:false, isWritable:false }, { pubkey:solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner:false, isWritable:false }, ]; return new TransactionInstruction({ keys, programId: ATA_PROGRAM_ID, data: new Uint8Array([]) }); } // Config via hidden meta tag function getCfg(){ const meta = document.getElementById('app-config'); const cluster = meta?.dataset.cluster || 'https://api.devnet.solana.com'; const programIdStr = meta?.dataset.programId || ''; const mintStr = meta?.dataset.mint || ''; const devWalletStr = meta?.dataset.devWallet || ''; const programId = programIdStr ? new PublicKey(programIdStr) : null; const mint = mintStr ? new PublicKey(mintStr) : null; const devWallet = devWalletStr ? new PublicKey(devWalletStr) : null; return { programId, mint, cluster, devWallet }; } async function applyConfig(){ try{ const res = await fetch('config.json', { cache:'no-store' }); if(!res.ok) return; const cfg = await res.json(); const meta = document.getElementById('app-config'); if(!meta) return; if(cfg.cluster) meta.dataset.cluster = cfg.cluster; if(cfg.programId) meta.dataset.programId = cfg.programId; if(cfg.mint) meta.dataset.mint = cfg.mint; if(cfg.devWallet) meta.dataset.devWallet = cfg.devWallet; }catch(e){ /*silent*/ } } let pendingReferrer = ''; async function readRefFromUrl(){ const params = new URLSearchParams(location.search); const ref = params.get('ref'); if(ref){ const el = $('referrer'); if (el) el.value = ref; else pendingReferrer = ref; } } let walletProvider = null; let walletPubkey = null; function shortAddr(pk){ const s=pk.toBase58(); return s.slice(0,4)+'…'+s.slice(-4); } function updateUIDisconnected(){ walletPubkey = null; const chip = $('walletChip'); if(chip){ chip.textContent = 'Not connected'; chip.style.display='none'; } const wa = $('walletAddr'); if(wa) wa.value = ''; const ref = $('reflinkRow'); if(ref) ref.style.display = 'none'; const ap = $('actionsPanel'); if(ap) ap.style.display = 'none'; const d = $('disconnectBtn'); if(d) d.style.display = 'none'; const c = $('connectBtn'); if(c) c.style.display = ''; const st = $('statusValue'); if(st) st.value = 'Connect wallet to participate'; } function updateUIConnected(){ if(!walletPubkey) return; const chip = $('walletChip'); if(chip){ chip.textContent = shortAddr(walletPubkey); chip.style.display=''; } const ref = $('reflinkRow'); if(ref) ref.style.display = ''; const ap = $('actionsPanel'); if(ap) ap.style.display = ''; const d = $('disconnectBtn'); if(d) d.style.display = ''; const c = $('connectBtn'); if(c) c.style.display = 'none'; const st = $('statusValue'); if(st) st.value = 'Ready'; } async function disconnectWallet(){ try{ if(walletProvider && typeof walletProvider.disconnect === 'function'){ try { await walletProvider.disconnect(); } catch { /* some wallets throw if not connected */ } } } finally { walletProvider = null; updateUIDisconnected(); } } async function connectWallet(){ try{ walletProvider = pickProvider(); if(!walletProvider){ log('No compatible wallet detected. Install Phantom/Solflare/Backpack/Glow/Exodus.', 'err'); return; } // Attach events if provided by wallet try{ if (walletProvider.on) { walletProvider.on('connect', () => { if(walletProvider?.publicKey){ walletPubkey = new PublicKey(walletProvider.publicKey.toString()); $('walletAddr').value = walletPubkey.toBase58(); updateUIConnected(); updateUserBalance(); updateReflink(); } }); walletProvider.on('disconnect', () => { updateUIDisconnected(); }); } }catch{} const res = await walletProvider.connect(); const pubkey = new PublicKey((res?.publicKey ?? walletProvider.publicKey).toString()); walletPubkey = pubkey; $('walletAddr').value = pubkey.toBase58(); await readRefFromUrl(); log(`Connected ${pubkey.toBase58()}`, 'ok'); updateUIConnected(); updateUserBalance(); updateReflink(); // Check if user account exists and toggle Prepare button visibility const { programId } = getCfg(); await withConn(async (conn)=>{ const exists = await userExists(conn, programId, walletPubkey); const prep = document.getElementById('registerBtn'); if (prep) prep.style.display = exists ? 'none' : ''; const st = $('statusValue'); if(st) st.value = exists ? 'Ready' : 'Ready — account will be prepared on your first purchase'; }); }catch(e){ log(`Connect failed: ${e.message}`,'err'); console.error(e); } } async function withConn(fn){ const {cluster}=getCfg(); const conn=new Connection(cluster,'confirmed'); return fn(conn); } async function userExists(conn, programId, owner){ try { const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId); const info = await conn.getAccountInfo(userPda); return !!info; } catch { return false; } } function logSendError(e){ try{ if (e && typeof e.getLogs === 'function') { e.getLogs().then((logs)=>console.error('Tx logs:', logs)).catch(()=>{}); } else if (e?.logs) { console.error('Tx logs:', e.logs); } }catch{} } async function ensureAta(conn, owner, mint){ const ata = await findAta(mint, owner); const acc = await conn.getAccountInfo(ata); if(!acc) return { ata, createIx: createAtaIx(owner, ata, owner, mint) }; return { ata }; } // Actions - Updated for our new program let __lastPot = null; async function updatePot(){ try{ const { programId, cluster } = getCfg(); if(!programId){ const s=$('statusValue'); if(s) s.value='Not configured'; return; } const conn = new Connection(cluster,'confirmed'); // Use our new vault PDA const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId); const lamports = await conn.getBalance(vaultPda, 'confirmed'); const solNum = lamports/1_000_000_000; const sol = solNum.toFixed(3); const potEl = $('potValue'); if(potEl) potEl.value = `${sol} SOL`; const potEl2 = $('potValue2'); if(potEl2) potEl2.value = `${sol} SOL`; const potBig = document.getElementById('potBig'); if(potBig) potBig.textContent = sol; // delta animation const deltaEl = document.getElementById('potDelta'); if(deltaEl !== null){ if(__lastPot !== null){ const d = solNum - __lastPot; if(Math.abs(d) >= 0.001){ deltaEl.textContent = (d>0?'+':'')+d.toFixed(3); deltaEl.classList.remove('pos','neg','show'); deltaEl.classList.add(d>0?'pos':'neg'); // force reflow for animation restart void deltaEl.offsetWidth; deltaEl.classList.add('show'); setTimeout(()=>deltaEl.classList.remove('show'), 1200); } } __lastPot = solNum; } }catch(e){ /*silent*/ } } async function updateUserBalance(){ try{ if(!walletPubkey) return; const { mint, cluster } = getCfg(); const conn = new Connection(cluster,'confirmed'); const ata = await findAta(mint, walletPubkey); const bal = await conn.getTokenAccountBalance(ata).catch(()=>null); const v = bal?.value?.uiAmountString ?? '0'; const el = $('userBalance'); if(el) el.value = v; // Auto-populate sell field with user's raw token balance (9 decimals) const rawAmount = bal?.value?.amount ?? '0'; const sellEl = $('sellAmount'); if(sellEl) sellEl.value = rawAmount; // Update vault fields const keysEl = $('userKeys'); if(keysEl) keysEl.value = v; const keys2El = $('userKeys2'); if(keys2El) keys2El.value = v; // if there's a second field const earningsEl = $('userEarnings'); if(earningsEl) earningsEl.value = '0.000 SOL'; // placeholder }catch(e){ /*silent*/ } } function updateReflink(){ if(!walletPubkey) return; const url = new URL(location.href); url.searchParams.set('ref', walletPubkey.toBase58()); const el = $('reflink'); if(el) el.value = url.toString(); const el2 = $('reflink2'); if(el2) el2.value = url.toString(); } async function register(){ try{ if(!walletPubkey) return log('Connect wallet first','warn'); const { programId, mint } = getCfg(); if(!programId||!mint){ const s=$('statusValue'); if(s) s.value='Not configured'; return; } const owner=walletPubkey; await withConn(async (conn)=>{ // Use our new PDA seeds const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId); const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId); const ata = await findAta(mint, owner); // Our new program uses register_user without referrer args const data = await buildIx('register_user', new Uint8Array([])); const keys=[ { pubkey:statePda, isSigner:false, isWritable:false }, { pubkey:userPda, isSigner:false, isWritable:true }, { pubkey:ata, isSigner:false, isWritable:true }, { pubkey:mint, isSigner:false, isWritable:false }, { pubkey:owner, isSigner:true, isWritable:true }, { pubkey:SystemProgram.programId, isSigner:false, isWritable:false }, { pubkey:TOKEN_PROGRAM_ID, isSigner:false, isWritable:false }, { pubkey:ATA_PROGRAM_ID, isSigner:false, isWritable:false }, ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction().add(ix); // No manual ATA creation needed const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash=blockhash; tx.feePayer=owner; const signed = await walletProvider.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize()); await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed'); log(`✅ Registered: ${sig}`,'ok'); }); }catch(e){ log(`Register failed: ${e.message}`,'err'); logSendError(e); } } async function buy(){ try{ if(!walletPubkey) return log('Connect wallet first','warn'); const { programId, mint, devWallet } = getCfg(); if(!programId||!mint){ const s=$('statusValue'); if(s) s.value='Not configured'; return; } const owner=walletPubkey; const solAmount = parseFloat($('buyAmount').value||'0'); const lamports = Math.floor(solAmount*1_000_000_000); if(lamports<=0) return log('Invalid amount','warn'); await withConn(async (conn)=>{ // Ensure user exists; auto-register if needed const exists = await userExists(conn, programId, owner); if(!exists){ log('User not found; registering...','warn'); await register(); } // Use our new PDA seeds and account structure const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId); const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId); const ata = await findAta(mint, owner); // Check if ATA exists, create if needed const ataAccount = await conn.getAccountInfo(ata); if (!ataAccount) { log('Creating token account first...', 'warn'); const createAtaTx = new Transaction().add(createAtaIx(owner, ata, owner, mint)); const {blockhash: ataBlockhash, lastValidBlockHeight: ataLastValid} = await conn.getLatestBlockhash('confirmed'); createAtaTx.recentBlockhash = ataBlockhash; createAtaTx.feePayer = owner; const signedAtaTx = await walletProvider.signTransaction(createAtaTx); const ataSig = await conn.sendRawTransaction(signedAtaTx.serialize()); await conn.confirmTransaction({signature: ataSig, blockhash: ataBlockhash, lastValidBlockHeight: ataLastValid}, 'confirmed'); log(`ATA created: ${ataSig}`, 'ok'); } const data = await buildIx('buy_tokens', le64(lamports)); const devWalletPk = devWallet ?? owner; // Build keys for our new buy_tokens instruction const keys = [ { pubkey: statePda, isSigner:false, isWritable:true }, // state { pubkey: vaultPda, isSigner:false, isWritable:true }, // vault { pubkey: mint, isSigner:false, isWritable:true }, // mint { pubkey: ata, isSigner:false, isWritable:true }, // buyer_token_account { pubkey: devWalletPk, isSigner:false, isWritable:true }, // dev_wallet { pubkey: owner, isSigner:true, isWritable:true }, // buyer { pubkey: SystemProgram.programId, isSigner:false, isWritable:false }, // system_program { pubkey: TOKEN_PROGRAM_ID, isSigner:false, isWritable:false } // token_program ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction().add(ix); const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash=blockhash; tx.feePayer=owner; const signed = await walletProvider.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize()); await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed'); await pushTickerItem(`BUY ${solAmount.toFixed(3)} SOL • ${shortAddr(owner)}`,'buy', owner.toBase58()); log(`✅ Buy: ${sig}`,'ok'); // Refresh balances after successful buy setTimeout(() => { updateUserBalance(); updatePot(); }, 2000); }); }catch(e){ log(`Buy failed: ${e.message}`,'err'); logSendError(e); } } async function sell(){ try{ if(!walletPubkey) return log('Connect wallet first','warn'); const { programId, mint } = getCfg(); const owner=walletPubkey; const tokensRaw = BigInt($('sellAmount').value||'0'); if(tokensRaw<=0n) return log('Invalid amount','warn'); await withConn(async (conn)=>{ // Use our new PDA seeds const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId); const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId); const ata = await findAta(mint, owner); const data = await buildIx('sell_tokens', le64(tokensRaw)); const keys=[ { pubkey: statePda, isSigner:false, isWritable:true }, { pubkey: vaultPda, isSigner:false, isWritable:true }, { pubkey: mint, isSigner:false, isWritable:true }, { pubkey: ata, isSigner:false, isWritable:true }, { pubkey: owner, isSigner:true, isWritable:true }, { pubkey: SystemProgram.programId, isSigner:false, isWritable:false }, { pubkey: TOKEN_PROGRAM_ID, isSigner:false, isWritable:false }, ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction().add(ix); const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash=blockhash; tx.feePayer=owner; const signed = await walletProvider.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize()); await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed'); const tk = Number(tokensRaw)/1_000_000_000; await pushTickerItem(`SELL ${tk.toFixed(3)} tk • ${shortAddr(owner)}`,'sell', owner.toBase58()); log(`✅ Sell: ${sig}`,'ok'); // Refresh balances after successful sell setTimeout(() => { updateUserBalance(); updatePot(); }, 2000); }); }catch(e){ log(`Sell failed: ${e.message}`,'err'); logSendError(e); } } async function withdraw(){ try{ if(!walletPubkey) return log('Connect wallet first','warn'); const { programId, mint } = getCfg(); const owner=walletPubkey; await withConn(async (conn)=>{ // Use our new PDA seeds and withdraw_dividends instruction const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId); const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId); const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId); const ata = await findAta(mint, owner); const data = await buildIx('withdraw_dividends', new Uint8Array([])); const keys=[ { pubkey: statePda, isSigner:false, isWritable:false }, { pubkey: vaultPda, isSigner:false, isWritable:true }, { pubkey: userPda, isSigner:false, isWritable:true }, { pubkey: ata, isSigner:false, isWritable:false }, { pubkey: mint, isSigner:false, isWritable:false }, { pubkey: owner, isSigner:true, isWritable:true }, { pubkey: SystemProgram.programId, isSigner:false, isWritable:false }, ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction().add(ix); const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash=blockhash; tx.feePayer=owner; const signed = await walletProvider.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize()); await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed'); log(`✅ Withdraw: ${sig}`,'ok'); // Refresh balances after successful withdraw setTimeout(() => { updateUserBalance(); updatePot(); }, 2000); }); }catch(e){ log(`Withdraw failed: ${e.message}`,'err'); logSendError(e); } } // Reflink copy function copyRef(){ const el = $('reflink'); if(!el || !el.value) return log('Nothing to copy','warn'); navigator.clipboard.writeText(el.value); log('Reflink copied to clipboard','ok'); } // Tabs & countdown function initTabs(){ const tabs = document.querySelectorAll('.tab'); const contents = { purchase: document.getElementById('tab-purchase'), vault: document.getElementById('tab-vault'), referrals: document.getElementById('tab-referrals') }; tabs.forEach((tab) => { tab.addEventListener('click', () => { const target = tab.dataset.tab; tabs.forEach((t) => t.classList.remove('active')); Object.values(contents).forEach((c) => c && c.classList.remove('active')); tab.classList.add('active'); if (contents[target]) { contents[target].classList.add('active'); contents[target].style.display = ''; } }); }); } function addEventListeners(){ const connectBtn = $('connectBtn'); if(connectBtn) connectBtn.addEventListener('click', connectWallet); const disconnectBtn = $('disconnectBtn'); if(disconnectBtn) disconnectBtn.addEventListener('click', disconnectWallet); const registerBtn = $('registerBtn'); if(registerBtn) registerBtn.addEventListener('click', register); const buyBtn = $('buyBtn'); if(buyBtn) buyBtn.addEventListener('click', buy); const sellBtn = $('sellBtn'); if(sellBtn) sellBtn.addEventListener('click', sell); const withdrawBtn = $('withdrawBtn'); if(withdrawBtn) withdrawBtn.addEventListener('click', withdraw); const withdrawBtn2 = $('withdrawBtn2'); if(withdrawBtn2) withdrawBtn2.addEventListener('click', withdraw); const copyRefBtn = $('copyRef'); if(copyRefBtn) copyRefBtn.addEventListener('click', copyRef); const copyRefBtn2 = $('copyRef2'); if(copyRefBtn2) copyRefBtn2.addEventListener('click', copyRef); } // Money rain animation (working version from website-solana) function initMoneyRain(count=72){ const wrap = document.getElementById('moneyRain'); if(!wrap) return; wrap.innerHTML = ''; const vw = Math.max(window.innerWidth, document.documentElement.clientWidth); for(let i=0;i{ clearTimeout(to); to=setTimeout(()=>initMoneyRain(count), 200); }, { passive:true }); } // Initialize async function init(){ log('🚀 PoWH3d Lottery initializing...'); populateWalletSelect(); await applyConfig(); initTabs(); addEventListeners(); updateUIDisconnected(); // Load existing ticker data from server await loadTickerFromServer(); // Set up periodic updates setInterval(updatePot, 5000); setInterval(pollRecentActivity, 10000); // Periodically refresh ticker from server to get updates from other users setInterval(loadTickerFromServer, 15000); // Every 15 seconds initMoneyRain(); // Start the money rain! updatePot(); // initial load log('✅ Ready!'); } // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();