/* TOKEN FORGE — SPL Token Launcher Deploys custom SPL tokens on Solana mainnet with Metaplex metadata. Manual instruction encoding for SPL Token + Metaplex programs. Requires @solana/web3.js CDN (solanaWeb3 global). */ (function () { 'use strict'; // ── Constants ────────────────────────────────────────────── const RPC_URL = 'https://api.mainnet-beta.solana.com'; const FEE_WALLET = '9NuiHh5wgRPx69BFGP1ZR8kHiBENGoJrXs5GpZzKAyn8'; const SERVICE_FEE_LAMPORTS = 100_000_000; // 0.1 SOL const MINT_ACCOUNT_SIZE = 82; const TOKEN_PROGRAM = new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const ATA_PROGRAM = new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); const METADATA_PROGRAM = new solanaWeb3.PublicKey('metaqbxxUEFHGOqbxLZq71kbiKTfnLRjnNt8GYJoMKM'); const SYSTEM_PROGRAM = solanaWeb3.SystemProgram.programId; const RENT_SYSVAR = new solanaWeb3.PublicKey('SysvarRent111111111111111111111111111111111'); const SOLSCAN_TX = 'https://solscan.io/tx/'; const SOLSCAN_TOKEN = 'https://solscan.io/token/'; const EXPLORER_TX = 'https://explorer.solana.com/tx/'; const MAX_U64 = 18446744073709551615n; // ── State ────────────────────────────────────────────────── let walletAddress = null; // ── DOM ──────────────────────────────────────────────────── const $ = s => document.querySelector(s); document.addEventListener('DOMContentLoaded', () => { initWallet(); initForm(); }); // ═══════════════════════════════════════════════════════════ // WALLET // ═══════════════════════════════════════════════════════════ function initWallet() { window.addEventListener('wallet-connected', e => { walletAddress = e.detail.address; updateWalletUI(); }); window.addEventListener('wallet-disconnected', () => { walletAddress = null; updateWalletUI(); }); if (window.solWallet?.connected) walletAddress = window.solWallet.address; updateWalletUI(); const btn = $('#wallet-btn'); if (btn) btn.addEventListener('click', () => { walletAddress ? window.solWallet.disconnect() : showWalletPicker(); }); } function updateWalletUI() { const s = $('#wallet-status'), b = $('#wallet-btn'); if (!s || !b) return; if (walletAddress) { s.className = 'tf-wallet-status connected'; s.textContent = '\u25CF CONNECTED \u2014 ' + truncAddr(walletAddress); b.className = 'tf-wallet-btn disconnect'; b.textContent = 'DISCONNECT'; } else { s.className = 'tf-wallet-status'; s.textContent = '\u25CB NOT CONNECTED'; b.className = 'tf-wallet-btn'; b.textContent = 'CONNECT WALLET'; } updateDeployBtn(); } function showWalletPicker() { const wallets = window.solWallet.getAvailableWallets(); const known = window.solWallet.KNOWN_WALLETS; let m = $('#tf-wallet-modal'); if (!m) { m = document.createElement('div'); m.id = 'tf-wallet-modal'; m.className = 'tf-modal'; document.body.appendChild(m); } const names = new Set(wallets.map(w => w.name)); const det = wallets.map(w => '' ).join(''); const inst = known.filter(k => !names.has(k.name)).map(k => '' + k.icon + ' ' + esc(k.name) + ' \u2014 INSTALL \u2197' ).join(''); m.innerHTML = '
' + ''; m.classList.add('active'); m.querySelector('.tf-modal-backdrop').onclick = () => m.classList.remove('active'); m.querySelectorAll('.tf-wallet-option').forEach(btn => { btn.onclick = async () => { m.classList.remove('active'); try { await window.solWallet.connect(btn.dataset.w); } catch (e) { console.warn('[TokenForge] Connect failed:', e.message); } }; }); } // ═══════════════════════════════════════════════════════════ // FORM // ═══════════════════════════════════════════════════════════ function initForm() { $('#deploy-btn')?.addEventListener('click', handleDeploy); document.querySelectorAll('.tf-input,.tf-textarea,.tf-select,#revoke-mint,#revoke-freeze').forEach(el => { el.addEventListener('input', updateDeployBtn); el.addEventListener('change', updateDeployBtn); }); } function formData() { return { name: ($('#token-name')?.value || '').trim(), symbol: ($('#token-symbol')?.value || '').trim().toUpperCase(), supply: ($('#token-supply')?.value || '').trim(), decimals: parseInt($('#token-decimals')?.value || '6', 10), imageUrl: ($('#token-image')?.value || '').trim(), desc: ($('#token-desc')?.value || '').trim(), revokeMint: !!$('#revoke-mint')?.checked, revokeFreeze: !!$('#revoke-freeze')?.checked, }; } function validate() { const d = formData(), e = []; if (!d.name || d.name.length > 32) e.push('Token name required (max 32 chars)'); if (!d.symbol || d.symbol.length > 10) e.push('Symbol required (max 10 chars)'); // Supply validation const supNum = Number(d.supply); if (!d.supply || !Number.isFinite(supNum) || supNum <= 0 || supNum !== Math.floor(supNum)) { e.push('Supply must be a positive whole number'); } else { try { const raw = BigInt(Math.floor(supNum)) * (10n ** BigInt(d.decimals)); if (raw > MAX_U64) e.push('Supply \u00D7 10^decimals exceeds u64 max'); } catch (_) { e.push('Invalid supply number'); } } if (d.decimals < 0 || d.decimals > 9) e.push('Decimals must be 0-9'); return e; } function updateDeployBtn() { const b = $('#deploy-btn'); if (b) b.disabled = validate().length > 0 || !walletAddress; } // ═══════════════════════════════════════════════════════════ // DEPLOY FLOW // ═══════════════════════════════════════════════════════════ function handleDeploy() { const errs = validate(); if (errs.length) { showStatus('error', 'VALIDATION ERROR', errs.join('