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;
}