SHA256
198 lines
7.3 KiB
JavaScript
198 lines
7.3 KiB
JavaScript
import { renderHeader } from '../components/header.js';
|
|
import { formatSol, getBalanceSol, transferSol, createSolanaWalletFromPrivateBase58 } from '../services/solana-wallet-service.js';
|
|
|
|
export const pageMeta = { id: 'devnet-topup-view', title: 'Пополнение DEVNET', showAppChrome: false };
|
|
|
|
const DEVNET_ENDPOINT = 'https://api.devnet.solana.com';
|
|
const SENDER_PRIVATE_32_BASE58 = '6xqAuKYvA8qrCdAkcw7Y8aMgvBnYk8JLxWLma5BzbAvu';
|
|
const TRANSFER_AMOUNT_SOL = 0.02;
|
|
|
|
function readWalletFromUrl() {
|
|
try {
|
|
const url = new URL(window.location.href);
|
|
return String(url.searchParams.get('wallet') || '').trim();
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function render() {
|
|
const screen = document.createElement('section');
|
|
screen.className = 'stack';
|
|
screen.style.width = '100%';
|
|
screen.style.justifyItems = 'center';
|
|
|
|
const targetWallet = readWalletFromUrl();
|
|
|
|
const senderBox = document.createElement('div');
|
|
senderBox.className = 'card stack';
|
|
senderBox.style.width = 'min(100%, 320px)';
|
|
senderBox.innerHTML = `
|
|
<strong>Тестовый DEVNET-кошелёк</strong>
|
|
<p class="meta-muted" id="devnet-topup-sender-address">Адрес: ...</p>
|
|
<p class="meta-muted" id="devnet-topup-sender-balance">Баланс: ...</p>
|
|
`;
|
|
|
|
const targetBox = document.createElement('div');
|
|
targetBox.className = 'card stack';
|
|
targetBox.style.width = 'min(100%, 320px)';
|
|
targetBox.innerHTML = `
|
|
<strong>Кошелёк получателя</strong>
|
|
<p class="meta-muted" style="word-break:break-all;">${targetWallet || 'Не передан параметр wallet'}</p>
|
|
<p class="meta-muted">Сумма перевода: ${TRANSFER_AMOUNT_SOL} SOL</p>
|
|
<p class="meta-muted" id="devnet-topup-target-balance">Баланс: ...</p>
|
|
`;
|
|
|
|
const status = document.createElement('p');
|
|
status.className = 'meta-muted';
|
|
status.style.width = 'min(100%, 320px)';
|
|
status.style.overflowWrap = 'anywhere';
|
|
status.style.wordBreak = 'break-word';
|
|
status.style.whiteSpace = 'pre-wrap';
|
|
status.textContent = `Готово к пополнению.\nСеть: DEVNET (${DEVNET_ENDPOINT})`;
|
|
|
|
const amountHint = document.createElement('p');
|
|
amountHint.style.width = 'min(100%, 320px)';
|
|
amountHint.style.display = 'none';
|
|
amountHint.style.margin = '0';
|
|
amountHint.style.fontSize = '22px';
|
|
amountHint.style.fontWeight = '800';
|
|
amountHint.style.lineHeight = '1.25';
|
|
amountHint.style.color = '#1fc97a';
|
|
amountHint.style.textAlign = 'center';
|
|
amountHint.textContent = '';
|
|
|
|
const balanceHint = document.createElement('p');
|
|
balanceHint.className = 'meta-muted';
|
|
balanceHint.style.width = 'min(100%, 320px)';
|
|
balanceHint.style.display = 'none';
|
|
balanceHint.style.margin = '0';
|
|
balanceHint.style.textAlign = 'center';
|
|
balanceHint.style.fontSize = '14px';
|
|
balanceHint.style.color = 'rgba(255,255,255,0.9)';
|
|
balanceHint.textContent = '';
|
|
|
|
const closeHint = document.createElement('p');
|
|
closeHint.style.width = 'min(100%, 320px)';
|
|
closeHint.style.display = 'none';
|
|
closeHint.style.margin = '0';
|
|
closeHint.style.fontSize = '26px';
|
|
closeHint.style.fontWeight = '800';
|
|
closeHint.style.lineHeight = '1.25';
|
|
closeHint.style.color = '#1fc97a';
|
|
closeHint.style.textAlign = 'center';
|
|
closeHint.textContent = 'Можете закрыть эту страницу и продолжить регистрацию.';
|
|
|
|
const fillBtn = document.createElement('button');
|
|
fillBtn.className = 'primary-btn';
|
|
fillBtn.type = 'button';
|
|
fillBtn.textContent = `Пополнить на ${TRANSFER_AMOUNT_SOL} SOL`;
|
|
|
|
const actions = document.createElement('div');
|
|
actions.className = 'auth-footer-actions';
|
|
actions.style.width = 'min(100%, 320px)';
|
|
actions.style.justifySelf = 'center';
|
|
actions.append(fillBtn);
|
|
|
|
let senderAddress = '';
|
|
let senderKeypair = null;
|
|
|
|
const updateSenderBalance = async () => {
|
|
if (!senderAddress) return;
|
|
const balance = await getBalanceSol({ endpoint: DEVNET_ENDPOINT, address: senderAddress });
|
|
const senderBalanceEl = senderBox.querySelector('#devnet-topup-sender-balance');
|
|
if (senderBalanceEl) senderBalanceEl.textContent = `Баланс: ${formatSol(balance.sol, 6)} SOL`;
|
|
};
|
|
|
|
const updateTargetBalance = async () => {
|
|
if (!targetWallet) return null;
|
|
const balance = await getBalanceSol({ endpoint: DEVNET_ENDPOINT, address: targetWallet });
|
|
const targetBalanceEl = targetBox.querySelector('#devnet-topup-target-balance');
|
|
if (targetBalanceEl) targetBalanceEl.textContent = `Баланс: ${formatSol(balance.sol, 6)} SOL`;
|
|
return balance.sol;
|
|
};
|
|
|
|
fillBtn.addEventListener('click', async () => {
|
|
if (!targetWallet) {
|
|
status.textContent = 'Ошибка: в URL не передан параметр wallet.';
|
|
return;
|
|
}
|
|
if (!senderKeypair) {
|
|
status.textContent = 'Ошибка: кошелёк отправителя не инициализирован.';
|
|
return;
|
|
}
|
|
|
|
fillBtn.disabled = true;
|
|
status.textContent = 'Отправляем перевод...';
|
|
amountHint.style.display = 'none';
|
|
balanceHint.style.display = 'none';
|
|
closeHint.style.display = 'none';
|
|
|
|
try {
|
|
const tx = await transferSol({
|
|
endpoint: DEVNET_ENDPOINT,
|
|
fromKeypair: senderKeypair,
|
|
toAddress: targetWallet,
|
|
amountSol: TRANSFER_AMOUNT_SOL,
|
|
});
|
|
await updateSenderBalance();
|
|
const targetBalanceSol = await updateTargetBalance();
|
|
status.textContent = `Транзакция прошла.\nSignature: ${tx.signature}`;
|
|
status.style.fontSize = '13px';
|
|
status.style.fontWeight = '500';
|
|
status.style.color = 'rgba(255,255,255,0.88)';
|
|
fillBtn.style.display = 'none';
|
|
actions.style.display = 'none';
|
|
amountHint.style.display = '';
|
|
amountHint.textContent = `Кошелёк пополнен на ${TRANSFER_AMOUNT_SOL} SOL`;
|
|
balanceHint.style.display = '';
|
|
balanceHint.textContent = `Новый баланс: ${formatSol(targetBalanceSol || 0, 6)} SOL`;
|
|
closeHint.style.display = '';
|
|
} catch (error) {
|
|
status.textContent = `Ошибка перевода: ${error?.message || 'unknown'}`;
|
|
status.style.fontSize = '';
|
|
status.style.fontWeight = '';
|
|
status.style.color = '';
|
|
amountHint.style.display = 'none';
|
|
balanceHint.style.display = 'none';
|
|
closeHint.style.display = 'none';
|
|
} finally {
|
|
fillBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
(async () => {
|
|
try {
|
|
const sender = await createSolanaWalletFromPrivateBase58(SENDER_PRIVATE_32_BASE58);
|
|
senderAddress = sender.address;
|
|
senderKeypair = sender.keypair;
|
|
const senderAddressEl = senderBox.querySelector('#devnet-topup-sender-address');
|
|
if (senderAddressEl) senderAddressEl.textContent = `Адрес: ${senderAddress}`;
|
|
await updateSenderBalance();
|
|
await updateTargetBalance();
|
|
if (!targetWallet) {
|
|
fillBtn.disabled = true;
|
|
status.textContent = 'Передайте адрес получателя в параметре wallet.';
|
|
}
|
|
} catch (error) {
|
|
fillBtn.disabled = true;
|
|
status.textContent = `Ошибка инициализации отправителя: ${error?.message || 'unknown'}`;
|
|
}
|
|
})();
|
|
|
|
screen.append(
|
|
renderHeader({
|
|
title: 'DEVNET пополнение',
|
|
}),
|
|
senderBox,
|
|
targetBox,
|
|
status,
|
|
amountHint,
|
|
balanceHint,
|
|
closeHint,
|
|
actions,
|
|
);
|
|
|
|
return screen;
|
|
}
|