SHA256
175 lines
7.2 KiB
JavaScript
175 lines
7.2 KiB
JavaScript
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 = `
|
|
<p class="auth-copy">Можете или пополнить счёт тестовыми соланами и продолжить регистрацию.</p>
|
|
<div class="card stack" style="padding:12px; max-width:320px;">
|
|
<div class="field-label" style="margin-bottom:6px;">Кошелёк для пополнения (client key = Solana wallet)</div>
|
|
</div>
|
|
<div class="stack" style="gap:6px;">
|
|
<p class="meta-muted">Или можете отдельно открыть страницу тестового пополнения.</p>
|
|
<a class="link-card" id="topup-site-link" href="${getTopupSiteUrl(state.registrationPayment.walletAddress || '')}" target="_blank" rel="noreferrer">Открыть страницу тестового пополнения</a>
|
|
</div>
|
|
`;
|
|
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;
|
|
}
|