import { renderHeader } from '../components/header.js'; import { SHINE_USERS_ECONOMY_CONFIG_SEED, SHINE_USERS_PROGRAM_ID, SOLANA_CLUSTER, } from '../solana-programs.js'; import { state } from '../state.js'; import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js'; export const pageMeta = { id: 'solana-users-init-view', title: 'Solana Init (users)' }; let solanaLibPromise = null; function loadSolanaLib() { if (!solanaLibPromise) { solanaLibPromise = loadSolanaWeb3(); } return solanaLibPromise; } function getProvider() { const p = globalThis?.solana; if (!p || !p.isPhantom) return null; return p; } function shortAddr(value = '') { const v = String(value || '').trim(); if (v.length < 12) return v; return `${v.slice(0, 6)}...${v.slice(-6)}`; } export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack'; const card = document.createElement('div'); card.className = 'card stack'; const status = document.createElement('p'); status.className = 'meta-muted'; status.textContent = 'Подключите кошелёк и выполните init_users_economy_config.'; const programId = SHINE_USERS_PROGRAM_ID; const programInput = document.createElement('input'); programInput.className = 'input'; programInput.type = 'text'; programInput.readOnly = true; programInput.value = programId; const rpcInput = document.createElement('input'); rpcInput.className = 'input'; rpcInput.type = 'text'; rpcInput.readOnly = true; rpcInput.value = String(state.entrySettings.solanaServer || ''); const walletLine = document.createElement('p'); walletLine.className = 'meta-muted'; walletLine.textContent = 'Кошелёк: не подключен'; const economyPdaLine = document.createElement('p'); economyPdaLine.className = 'meta-muted'; economyPdaLine.textContent = 'PDA economy config: —'; const txLine = document.createElement('p'); txLine.className = 'meta-muted'; txLine.textContent = 'TX: —'; const connectBtn = document.createElement('button'); connectBtn.className = 'text-btn'; connectBtn.type = 'button'; connectBtn.textContent = 'Подключить кошелёк'; const initBtn = document.createElement('button'); initBtn.className = 'primary-btn'; initBtn.type = 'button'; initBtn.textContent = 'Запустить init_users_economy_config'; initBtn.disabled = true; let provider = null; let walletPubkey = null; let economyPda = null; async function recomputePda() { const solana = await loadSolanaLib(); const pid = new solana.PublicKey(programId); const [pda] = solana.PublicKey.findProgramAddressSync( [new TextEncoder().encode(SHINE_USERS_ECONOMY_CONFIG_SEED)], pid, ); economyPda = pda; economyPdaLine.textContent = `PDA economy config: ${pda.toBase58()}`; } connectBtn.addEventListener('click', async () => { status.textContent = 'Подключение кошелька...'; try { provider = getProvider(); if (!provider) throw new Error('Phantom не найден'); const result = await provider.connect(); walletPubkey = result?.publicKey || provider.publicKey; if (!walletPubkey) throw new Error('Кошелёк не вернул public key'); walletLine.textContent = `Кошелёк: ${walletPubkey.toBase58()} (${shortAddr(walletPubkey.toBase58())})`; initBtn.disabled = false; status.textContent = 'Кошелёк подключен.'; } catch (e) { status.textContent = `Ошибка подключения: ${e?.message || 'unknown'}`; } }); initBtn.addEventListener('click', async () => { if (!provider || !walletPubkey || !economyPda) return; initBtn.disabled = true; status.textContent = 'Отправка транзакции...'; txLine.textContent = 'TX: —'; try { const solana = await loadSolanaLib(); const connection = new solana.Connection(String(state.entrySettings.solanaServer || ''), 'confirmed'); const programPubkey = new solana.PublicKey(programId); const ix = new solana.TransactionInstruction({ programId: programPubkey, keys: [ { pubkey: walletPubkey, isSigner: true, isWritable: true }, { pubkey: economyPda, isSigner: false, isWritable: true }, { pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false }, ], data: Uint8Array.from([1]), }); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); const tx = new solana.Transaction({ feePayer: walletPubkey, blockhash, lastValidBlockHeight, }).add(ix); const signed = await provider.signTransaction(tx); const sig = await connection.sendRawTransaction(signed.serialize(), { skipPreflight: false }); await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed'); txLine.textContent = `TX: ${sig}`; status.textContent = 'Успешно: init_users_economy_config выполнен.'; } catch (e) { const message = String(e?.message || 'unknown'); txLine.textContent = 'TX: ошибка'; if (message.toLowerCase().includes('already') || message.includes('1000')) { status.textContent = 'PDA уже инициализирован. Повторный init не требуется.'; } else { status.textContent = `Ошибка init: ${message}`; } } finally { initBtn.disabled = false; } }); void recomputePda(); card.append( (() => { const t = document.createElement('p'); t.className = 'meta-muted'; t.textContent = `Cluster: ${SOLANA_CLUSTER}`; return t; })(), (() => { const label = document.createElement('label'); label.className = 'field-label'; label.textContent = 'Program ID (shine_users)'; return label; })(), programInput, (() => { const label = document.createElement('label'); label.className = 'field-label'; label.textContent = 'RPC endpoint'; return label; })(), rpcInput, walletLine, economyPdaLine, txLine, connectBtn, initBtn, status, ); screen.append( renderHeader({ title: 'Solana Init (users)', leftAction: { label: '←', onClick: () => navigate('developer-settings-view') }, }), card, ); return screen; }