import { renderHeader } from '../components/header.js'; import { state } from '../state.js'; import { formatSol, getBalanceSol, getTopupSiteUrl, getWalletFromStoredDeviceKey, requestAirdropSol, transferSol, } from '../services/solana-wallet-service.js'; export const pageMeta = { id: 'wallet-view', title: 'Кошелёк' }; function nowRu() { return new Date().toLocaleString('ru-RU'); } export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack'; let walletCtx = null; let walletAddress = ''; const status = document.createElement('p'); status.className = 'meta-muted'; status.textContent = 'Инициализация wallet.key...'; const screenTitle = 'Кошелёк'; screen.append( renderHeader({ title: screenTitle, leftAction: { label: '←', onClick: () => navigate('profile-view') }, }), ); const card = document.createElement('div'); card.className = 'card stack'; const balanceWrap = document.createElement('div'); const balanceLabel = document.createElement('p'); balanceLabel.className = 'meta-muted'; balanceLabel.textContent = 'Баланс (Solana)'; const balanceValue = document.createElement('h2'); balanceValue.style.fontSize = '30px'; balanceValue.textContent = '— SOL'; const updatedLabel = document.createElement('p'); updatedLabel.className = 'meta-muted'; updatedLabel.textContent = 'Обновлено: —'; const endpointLabel = document.createElement('p'); endpointLabel.className = 'meta-muted'; endpointLabel.textContent = `RPC: ${state.entrySettings.solanaServer}`; balanceWrap.append(balanceLabel, balanceValue, updatedLabel, endpointLabel); const addressCard = document.createElement('div'); addressCard.className = 'card'; addressCard.style.padding = '10px'; addressCard.innerHTML = `

Публичный адрес (wallet.key = device.key)

`; card.append(balanceWrap, addressCard); const actions = document.createElement('div'); actions.className = 'stack'; actions.innerHTML = `
`; const copyBtn = actions.querySelector('#copy-address'); const refreshBtn = actions.querySelector('#refresh-balance'); const sendBtn = actions.querySelector('#send-sol'); const topupBtn = actions.querySelector('#topup-sol'); const addressEl = addressCard.querySelector('#wallet-address-value'); const setStatus = (text) => { status.textContent = String(text || ''); }; const refreshBalance = async () => { if (!walletAddress) { setStatus('Кошелёк не инициализирован.'); return; } refreshBtn.disabled = true; try { const balance = await getBalanceSol({ endpoint: state.entrySettings.solanaServer, address: walletAddress, }); balanceValue.textContent = `${formatSol(balance.sol, 6)} SOL`; updatedLabel.textContent = `Обновлено: ${nowRu()}`; endpointLabel.textContent = `RPC: ${balance.endpoint}`; setStatus('Баланс обновлён.'); } catch (error) { setStatus(`Не удалось получить баланс: ${error?.message || 'unknown'}`); } finally { refreshBtn.disabled = false; } }; copyBtn.addEventListener('click', async () => { if (!walletAddress) return; try { await navigator.clipboard.writeText(walletAddress); setStatus('Адрес скопирован в буфер обмена'); } catch { setStatus('Не удалось скопировать адрес в этом браузере'); } }); refreshBtn.addEventListener('click', () => { void refreshBalance(); }); sendBtn.addEventListener('click', async () => { if (!walletCtx?.keypair) { setStatus('Перевод недоступен: wallet.key не загружен.'); return; } const toAddress = window.prompt('Введите адрес получателя (Solana):', ''); if (!toAddress) return; const amountRaw = window.prompt('Введите сумму SOL для перевода:', '0.01'); if (!amountRaw) return; sendBtn.disabled = true; try { const tx = await transferSol({ endpoint: state.entrySettings.solanaServer, fromKeypair: walletCtx.keypair, toAddress, amountSol: Number(String(amountRaw || '').replace(',', '.')), }); setStatus(`Перевод отправлен. Signature: ${tx.signature}`); await refreshBalance(); } catch (error) { setStatus(`Ошибка перевода: ${error?.message || 'unknown'}`); } finally { sendBtn.disabled = false; } }); topupBtn.addEventListener('click', async () => { if (!walletAddress) { setStatus('Кошелёк не инициализирован.'); return; } const openSite = window.confirm( 'Кнопка будет переводить на отдельный сайт пополнения.\n\nНажмите OK, чтобы открыть сайт.\nНажмите Отмена, чтобы выполнить тестовое пополнение (airdrop 1 SOL на DevNet).' ); if (openSite) { window.open(getTopupSiteUrl(), '_blank', 'noopener,noreferrer'); setStatus('Открыт сайт пополнения. Пока также доступно тестовое пополнение.'); return; } topupBtn.disabled = true; try { const drop = await requestAirdropSol({ endpoint: state.entrySettings.solanaServer, address: walletAddress, amountSol: 1, }); setStatus(`Тестовое пополнение выполнено (airdrop 1 SOL). Signature: ${drop.signature}`); await refreshBalance(); } catch (error) { setStatus(`Ошибка тестового пополнения: ${error?.message || 'unknown'}`); } finally { topupBtn.disabled = false; } }); (async () => { try { walletCtx = await getWalletFromStoredDeviceKey({ login: state.session.login, storagePwd: state.session.storagePwdInMemory, }); walletAddress = walletCtx.address; addressEl.textContent = walletAddress; await refreshBalance(); } catch (error) { addressEl.textContent = 'wallet.key недоступен'; setStatus(`Не удалось инициализировать кошелёк: ${error?.message || 'unknown'}`); } })(); screen.append(card, actions, status); return screen; }