import { renderHeader } from '../components/header.js'; import { state } from '../state.js'; import { createSolanaWalletFromPrivateBase58, formatSol, getBalanceSol, getTopupSiteUrl, transferSol, } from '../services/solana-wallet-service.js'; import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js'; export const pageMeta = { id: 'topup-view', title: 'Пополнение solana счета', showAppChrome: false }; const DEVNET_ENDPOINT = 'https://api.devnet.solana.com'; const SENDER_PRIVATE_32_BASE58 = '6xqAuKYvA8qrCdAkcw7Y8aMgvBnYk8JLxWLma5BzbAvu'; const REGISTRATION_TOPUP_AMOUNT_SOL = 0.02; // Канонический Solana-адрес пополнения = публичный device-ключ из сгенерированного набора ключей. // Тот же путь, что в registration-payment-view (deriveUserWalletAddress); не выводим адрес // напрямую из пароля, иначе он расходится с device-ключом регистрации. async function clientWalletAddressFromBundle() { const keyBundle = state.registrationDraft.preGeneratedKeyBundle; if (!keyBundle || !keyBundle.clientPair) { throw new Error('Ключи ещё не сгенерированы. Вернитесь на экран регистрации.'); } const raw = atob(keyBundle.clientPair.publicKeyB64); const bytes = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i += 1) bytes[i] = raw.charCodeAt(i); const { PublicKey } = await loadSolanaWeb3(); return new PublicKey(bytes).toBase58(); } export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack'; const walletValue = document.createElement('input'); walletValue.className = 'input'; walletValue.type = 'text'; walletValue.value = state.registrationPayment.walletAddress || ''; walletValue.readOnly = true; walletValue.style.fontSize = '13px'; const status = document.createElement('p'); status.className = 'meta-muted'; status.textContent = 'Проверяем кошелек...'; status.style.whiteSpace = 'pre-wrap'; status.style.overflowWrap = 'anywhere'; status.style.wordBreak = 'break-word'; status.style.fontSize = '13px'; const copyButton = document.createElement('button'); copyButton.className = 'ghost-btn'; copyButton.type = 'button'; copyButton.textContent = 'Скопировать'; copyButton.addEventListener('click', async () => { try { await navigator.clipboard.writeText(walletValue.value); copyButton.textContent = 'Скопировано'; window.setTimeout(() => { copyButton.textContent = 'Скопировать'; }, 1500); } catch { window.alert('Не удалось скопировать номер кошелька.'); } }); const walletRow = document.createElement('div'); walletRow.className = 'inline-input-row'; walletRow.append(walletValue, copyButton); const card = document.createElement('div'); card.className = 'card stack'; card.innerHTML = `

Можете или пополнить счёт тестовыми соланами и продолжить регистрацию.

Кошелёк для пополнения (client key = Solana wallet)

Или можете отдельно открыть страницу тестового пополнения.

Открыть страницу тестового пополнения
`; card.children[1].append(walletRow); const testButton = document.createElement('button'); testButton.className = 'ghost-btn'; testButton.type = 'button'; testButton.textContent = 'Тестовое пополнение для регистрации'; testButton.addEventListener('click', async () => { const address = String(walletValue.value || '').trim(); if (!address) { window.alert('Адрес кошелька не найден.'); return; } if (!senderKeypair) { status.textContent = 'Ошибка: тестовый кошелёк отправителя ещё не инициализирован.'; return; } testButton.disabled = true; try { status.textContent = 'Отправляем тестовое пополнение для регистрации...'; const tx = await transferSol({ endpoint: DEVNET_ENDPOINT, fromKeypair: senderKeypair, toAddress: address, amountSol: REGISTRATION_TOPUP_AMOUNT_SOL, }); const bal = await getBalanceSol({ endpoint: DEVNET_ENDPOINT, address, }); state.registrationPayment.balanceSOL = String(bal.sol); status.style.fontSize = '12px'; status.textContent = [ 'Тестовое пополнение для регистрации выполнено.', `Кошелёк пополнен на ${REGISTRATION_TOPUP_AMOUNT_SOL} SOL.`, `Новый баланс: ${formatSol(bal.sol, 6)} SOL.`, `Signature: ${tx.signature}`, ].join('\n'); } catch (error) { status.style.fontSize = '13px'; status.textContent = `Ошибка тестового пополнения: ${error?.message || 'unknown'}`; } finally { testButton.disabled = false; } }); const backButton = document.createElement('button'); backButton.className = 'primary-btn'; backButton.type = 'button'; backButton.textContent = 'Продолжить регистрацию'; backButton.addEventListener('click', () => navigate('registration-payment-view')); const actions = document.createElement('div'); actions.className = 'auth-footer-actions'; actions.append(testButton, backButton); let senderKeypair = null; (async () => { try { if (!walletValue.value) { const address = await clientWalletAddressFromBundle(); state.registrationPayment.walletAddress = address; walletValue.value = address; } const topupSiteLink = card.querySelector('#topup-site-link'); if (topupSiteLink instanceof HTMLAnchorElement) { topupSiteLink.href = getTopupSiteUrl(walletValue.value); } const balance = await getBalanceSol({ endpoint: state.entrySettings.solanaServer, address: walletValue.value, }); state.registrationPayment.balanceSOL = String(balance.sol); status.textContent = `Текущий баланс: ${formatSol(balance.sol, 6)} SOL`; const sender = await createSolanaWalletFromPrivateBase58(SENDER_PRIVATE_32_BASE58); senderKeypair = sender.keypair; } catch (error) { status.textContent = `Не удалось получить баланс: ${error?.message || 'unknown'}`; } })(); screen.append( renderHeader({ title: 'Пополнение solana счета', leftAction: { label: '←', onClick: () => navigate('registration-payment-view') }, }), card, status, actions, ); return screen; }