/* global solanaWeb3 */ (() => { const { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction } = solanaWeb3; // CONSTANTS const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); // DOM helpers const $ = (id) => document.getElementById(id); const logEl = () => $('log'); const log = (msg, cls = '') => { const el = logEl(); if (!el) return; const line = document.createElement('div'); if (cls) line.className = cls; line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; el.prepend(line); }; // Buffer helpers function le64(n) { const b = new ArrayBuffer(8); new DataView(b).setBigUint64(0, BigInt(n), true); return new Uint8Array(b); } async function anchorDiscriminator(name) { const data = new TextEncoder().encode(`global:${name}`); const hash = await crypto.subtle.digest('SHA-256', data); return new Uint8Array(hash).slice(0, 8); } // PDA helpers async function findPdaPowh(programId) { const [pda] = await PublicKey.findProgramAddress([new TextEncoder().encode('powh')], programId); return pda; } async function findPdaUser(programId, owner) { const [pda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId); return pda; } async function getAssociatedTokenAddress(mint, owner) { const [addr] = await PublicKey.findProgramAddress( [owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID ); return addr; } function createATAInstruction(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 }, ]; const data = new Uint8Array([]); return new TransactionInstruction({ keys, programId: ASSOCIATED_TOKEN_PROGRAM_ID, data }); } // Build Anchor instruction with discriminators async function buildIx(name, argsBytes) { const disc = await anchorDiscriminator(name); const data = new Uint8Array(disc.length + argsBytes.length); data.set(disc, 0); data.set(argsBytes, disc.length); return data; } async function ensureWallet() { if (!window.solana || !window.solana.isPhantom) { throw new Error('Phantom wallet not found. Please install Phantom.'); } const resp = await window.solana.connect(); return new PublicKey(resp.publicKey.toString()); } function getConfig() { const programId = new PublicKey($('programId').value.trim()); const mintStr = $('mint').value.trim(); if (!mintStr) throw new Error('Please set the Mint address.'); const mint = new PublicKey(mintStr); const cluster = $('cluster').value; return { programId, mint, cluster }; } async function refreshDerived(pubkey) { try { const { programId } = getConfig(); const powhPda = await findPdaPowh(programId); const userPda = await findPdaUser(programId, pubkey); $('powhPda').value = powhPda.toBase58(); $('userPda').value = userPda.toBase58(); log(`Derived PDAs updated.`, 'ok'); } catch (e) { log(`PDA derivation failed: ${e.message}`, 'err'); } } async function connectWallet() { try { const pubkey = await ensureWallet(); $('walletInfo').textContent = `Connected: ${pubkey.toBase58()}`; $('walletAddr').value = pubkey.toBase58(); await refreshDerived(pubkey); } catch (e) { log(e.message, 'err'); } } async function withConn(fn) { const { cluster } = getConfig(); const conn = new Connection(cluster, 'confirmed'); return fn(conn); } async function ensureAta(conn, owner, mint) { const ata = await getAssociatedTokenAddress(mint, owner); const info = await conn.getAccountInfo(ata); if (!info) { log('ATA not found, creating...'); return { ata, createIx: createATAInstruction(owner, ata, owner, mint) }; } return { ata }; } async function handleRegister() { try { const { programId, mint } = getConfig(); const owner = await ensureWallet(); await withConn(async (conn) => { const powhPda = await findPdaPowh(programId); const userPda = await findPdaUser(programId, owner); const { ata, createIx } = await ensureAta(conn, owner, mint); // Build register_user(referrer: Option) instruction const refStr = $('referrer').value.trim(); const hasRef = !!refStr; let args = new Uint8Array([hasRef ? 1 : 0]); if (hasRef) { const ref = new PublicKey(refStr); args = new Uint8Array([...args, ...ref.toBytes()]); } const data = await buildIx('register_user', args); const keys = [ { 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: solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction(); if (createIx) tx.add(createIx); tx.add(ix); const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash = blockhash; tx.feePayer = owner; const signed = await window.solana.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize(), { skipPreflight: false }); await conn.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); $('ataAddr').value = ata.toBase58(); log(`✅ Registered. Tx: ${sig}`, 'ok'); }); } catch (e) { log(`Register failed: ${e.message}`, 'err'); } } async function handleBuy() { try { const { programId, mint } = getConfig(); const owner = await ensureWallet(); const solAmount = parseFloat($('buyAmount').value || '0'); const lamports = Math.floor(solAmount * 1_000_000_000); if (lamports <= 0) throw new Error('Invalid SOL amount'); await withConn(async (conn) => { const powhPda = await findPdaPowh(programId); const userPda = await findPdaUser(programId, owner); const { ata, createIx } = await ensureAta(conn, owner, mint); // buy(lamports_amount: u64) const data = await buildIx('buy', le64(lamports)); const refStr = $('referrer').value.trim(); const keys = [ { pubkey: powhPda, isSigner: false, isWritable: true }, { pubkey: userPda, isSigner: false, isWritable: true }, { pubkey: mint, isSigner: false, isWritable: true }, { pubkey: ata, isSigner: false, isWritable: true }, // Optional accounts (pass if referrer set, else omit) - our program currently expects them; include safely when provided ...(refStr ? [{ pubkey: await getAssociatedTokenAddress(mint, new PublicKey(refStr)), isSigner: false, isWritable: false }] : []), ...(refStr ? [{ pubkey: await findPdaUser(programId, new PublicKey(refStr)), 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 }, ]; const ix = new TransactionInstruction({ keys, programId, data }); const tx = new Transaction(); if (createIx) tx.add(createIx); tx.add(ix); const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash('confirmed'); tx.recentBlockhash = blockhash; tx.feePayer = owner; const signed = await window.solana.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize(), { skipPreflight: false }); await conn.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); log(`✅ Buy successful. Tx: ${sig}`, 'ok'); }); } catch (e) { log(`Buy failed: ${e.message}`, 'err'); } } async function handleSell() { try { const { programId, mint } = getConfig(); const owner = await ensureWallet(); const tokensRaw = BigInt($('sellAmount').value || '0'); if (tokensRaw <= 0n) throw new Error('Invalid token amount'); await withConn(async (conn) => { const powhPda = await findPdaPowh(programId); const userPda = await findPdaUser(programId, owner); const { ata } = await ensureAta(conn, owner, mint); const data = await buildIx('sell', le64(tokensRaw)); const keys = [ { pubkey: powhPda, isSigner: false, isWritable: true }, { pubkey: userPda, isSigner: false, isWritable: true }, { pubkey: mint, isSigner: false, isWritable: true }, { pubkey: ata, isSigner: false, isWritable: true }, { pubkey: owner, isSigner: true, isWritable: true }, { 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 window.solana.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize(), { skipPreflight: false }); await conn.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); log(`✅ Sell successful. Tx: ${sig}`, 'ok'); }); } catch (e) { log(`Sell failed: ${e.message}`, 'err'); } } async function handleWithdraw() { try { const { programId, mint } = getConfig(); const owner = await ensureWallet(); await withConn(async (conn) => { const powhPda = await findPdaPowh(programId); const userPda = await findPdaUser(programId, owner); const ata = await getAssociatedTokenAddress(mint, owner); const data = await buildIx('withdraw', new Uint8Array([])); const keys = [ { pubkey: powhPda, 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 }, ]; 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 window.solana.signTransaction(tx); const sig = await conn.sendRawTransaction(signed.serialize(), { skipPreflight: false }); await conn.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); log(`✅ Withdraw successful. Tx: ${sig}`, 'ok'); }); } catch (e) { log(`Withdraw failed: ${e.message}`, 'err'); } } // Wire up events window.addEventListener('DOMContentLoaded', () => { $('connectBtn').addEventListener('click', connectWallet); $('registerBtn').addEventListener('click', handleRegister); $('buyBtn').addEventListener('click', handleBuy); $('sellBtn').addEventListener('click', handleSell); $('withdrawBtn').addEventListener('click', handleWithdraw); $('cluster').addEventListener('change', () => log(`Cluster set to ${$('cluster').value}`, 'warn')); $('programId').addEventListener('change', async () => { try { await refreshDerived(new PublicKey($('walletAddr').value)); } catch {} }); }); })();