chore: зафиксированы оставшиеся локальные изменения

This commit is contained in:
AidarKC
2026-05-19 00:07:49 +03:00
parent a332ddc828
commit c27da63a3e
7 changed files with 268 additions and 77 deletions
+201 -1
View File
@@ -1,6 +1,8 @@
import { renderHeader } from '../components/header.js';
import { state } from '../state.js';
import {
createRandomSolanaWallet,
createSolanaWalletFromPrivateBase58,
formatSol,
getBalanceSol,
getTopupSiteUrl,
@@ -17,6 +19,7 @@ import {
} from '../services/arweave-wallet-service.js';
export const pageMeta = { id: 'wallet-view', title: 'Кошелёк' };
const SOLANA_PRIVATE_BASE58_MAX_LEN = 44;
function nowRu() {
return new Date().toLocaleString('ru-RU');
@@ -165,6 +168,203 @@ export function render({ navigate }) {
const sendBtn = actions.querySelector('#send-sol');
const topupBtn = actions.querySelector('#topup-sol');
const generatedCard = document.createElement('div');
generatedCard.className = 'card stack';
generatedCard.innerHTML = `
<h3 style="margin:0;">Создание нового кошелька Solana</h3>
<p class="meta-muted" style="margin:0;">Введите приватный ключ Base58 (32 байта) или сгенерируйте случайный.</p>
`;
const privateLabel = document.createElement('label');
privateLabel.className = 'meta-muted';
privateLabel.textContent = 'Приватный ключ (Base58, 32 байта)';
privateLabel.setAttribute('for', 'solana-private-base58-input');
const privateInput = document.createElement('input');
privateInput.id = 'solana-private-base58-input';
privateInput.type = 'text';
privateInput.placeholder = 'Введите приватный ключ Base58';
privateInput.maxLength = SOLANA_PRIVATE_BASE58_MAX_LEN;
privateInput.autocomplete = 'off';
privateInput.spellcheck = false;
const privateState = document.createElement('p');
privateState.className = 'meta-muted';
privateState.textContent = 'Ожидается Base58-строка приватного ключа.';
const generatedPublicLabel = document.createElement('label');
generatedPublicLabel.className = 'meta-muted';
generatedPublicLabel.textContent = 'Публичный ключ (Base58)';
generatedPublicLabel.setAttribute('for', 'solana-generated-public-key');
const generatedPublicInput = document.createElement('input');
generatedPublicInput.id = 'solana-generated-public-key';
generatedPublicInput.type = 'text';
generatedPublicInput.readOnly = true;
generatedPublicInput.placeholder = 'Будет сгенерирован после нажатия кнопки';
const generatedPrivateLabel = document.createElement('label');
generatedPrivateLabel.className = 'meta-muted';
generatedPrivateLabel.textContent = 'Сгенерированный приватный ключ (Base58)';
generatedPrivateLabel.setAttribute('for', 'solana-generated-private-key');
const generatedPrivateInput = document.createElement('input');
generatedPrivateInput.id = 'solana-generated-private-key';
generatedPrivateInput.type = 'text';
generatedPrivateInput.readOnly = true;
generatedPrivateInput.placeholder = 'Появится после генерации';
const generationActions = document.createElement('div');
generationActions.className = 'row';
generationActions.innerHTML = `
<button class="primary-btn" id="generate-random-solana" style="width:100%;">Сгенерировать случайный кошелёк</button>
<button class="primary-btn" id="generate-from-private-solana" style="width:100%;">Сгенерировать из приватного ключа</button>
`;
const copyGeneratedActions = document.createElement('div');
copyGeneratedActions.className = 'row';
copyGeneratedActions.innerHTML = `
<button class="text-btn" id="copy-generated-private-solana" style="width:100%;">Копировать приватный</button>
<button class="text-btn" id="copy-generated-public-solana" style="width:100%;">Копировать публичный</button>
`;
generatedCard.append(
privateLabel,
privateInput,
privateState,
generationActions,
generatedPrivateLabel,
generatedPrivateInput,
generatedPublicLabel,
generatedPublicInput,
copyGeneratedActions,
);
const randomGenerateBtn = generationActions.querySelector('#generate-random-solana');
const fromPrivateGenerateBtn = generationActions.querySelector('#generate-from-private-solana');
const copyGeneratedPrivateBtn = copyGeneratedActions.querySelector('#copy-generated-private-solana');
const copyGeneratedPublicBtn = copyGeneratedActions.querySelector('#copy-generated-public-solana');
const BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]+$/;
const validatePrivateInput = () => {
const value = String(privateInput.value || '').trim();
if (!value) {
privateState.textContent = 'Ожидается Base58-строка приватного ключа.';
return false;
}
if (!BASE58_RE.test(value)) {
privateState.textContent = 'Недопустимый формат: используйте только Base58.';
return false;
}
if (value.length > SOLANA_PRIVATE_BASE58_MAX_LEN) {
privateState.textContent = 'Слишком длинное значение.';
return false;
}
try {
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
let num = 0n;
for (const c of value) {
num = num * 58n + BigInt(alphabet.indexOf(c));
}
let hex = num.toString(16);
if (hex.length % 2) hex = `0${hex}`;
const decoded = hex ? hex.match(/.{1,2}/g)?.map((h) => parseInt(h, 16)) || [] : [];
let leadingZeros = 0;
while (leadingZeros < value.length && value[leadingZeros] === '1') leadingZeros += 1;
const byteLen = leadingZeros + decoded.length;
if (byteLen < 32) {
privateState.textContent = 'Слишком короткое значение: нужно 32 байта.';
return false;
}
if (byteLen > 32) {
privateState.textContent = 'Слишком длинное значение: нужно ровно 32 байта.';
return false;
}
} catch {
privateState.textContent = 'Ошибка декодирования Base58.';
return false;
}
privateState.textContent = 'Подходит';
return true;
};
privateInput.addEventListener('input', () => {
validatePrivateInput();
});
const setGenerationDisabled = (disabled) => {
randomGenerateBtn.disabled = disabled;
fromPrivateGenerateBtn.disabled = disabled;
copyGeneratedPrivateBtn.disabled = disabled;
copyGeneratedPublicBtn.disabled = disabled;
};
randomGenerateBtn.addEventListener('click', async () => {
setGenerationDisabled(true);
try {
const generated = await createRandomSolanaWallet();
if (modeToken !== activeModeToken) return;
generatedPrivateInput.value = generated.privateKey32Base58;
generatedPublicInput.value = generated.address;
privateState.textContent = 'Случайный кошелёк создан.';
setStatus('Случайный кошелёк Solana успешно сгенерирован.');
} catch (error) {
if (modeToken !== activeModeToken) return;
setStatus(`Ошибка генерации случайного кошелька: ${error?.message || 'unknown'}`);
} finally {
setGenerationDisabled(false);
}
});
fromPrivateGenerateBtn.addEventListener('click', async () => {
if (!validatePrivateInput()) {
setStatus('Исправьте приватный ключ перед генерацией.');
return;
}
setGenerationDisabled(true);
try {
const generated = await createSolanaWalletFromPrivateBase58(privateInput.value);
if (modeToken !== activeModeToken) return;
generatedPrivateInput.value = generated.privateKey32Base58;
generatedPublicInput.value = generated.address;
privateState.textContent = 'Подходит';
setStatus('Публичный ключ сгенерирован из введённого приватного ключа.');
} catch (error) {
if (modeToken !== activeModeToken) return;
setStatus(`Ошибка генерации из приватного ключа: ${error?.message || 'unknown'}`);
} finally {
setGenerationDisabled(false);
}
});
copyGeneratedPrivateBtn.addEventListener('click', async () => {
const value = String(generatedPrivateInput.value || '').trim();
if (!value) {
setStatus('Сначала сгенерируйте приватный ключ.');
return;
}
try {
await navigator.clipboard.writeText(value);
setStatus('Приватный ключ скопирован.');
} catch {
setStatus('Не удалось скопировать приватный ключ в этом браузере.');
}
});
copyGeneratedPublicBtn.addEventListener('click', async () => {
const value = String(generatedPublicInput.value || '').trim();
if (!value) {
setStatus('Сначала сгенерируйте публичный ключ.');
return;
}
try {
await navigator.clipboard.writeText(value);
setStatus('Публичный ключ скопирован.');
} catch {
setStatus('Не удалось скопировать публичный ключ в этом браузере.');
}
});
const refreshBalance = async () => {
if (!walletAddress) {
setStatus('Кошелёк не инициализирован.');
@@ -265,7 +465,7 @@ export function render({ navigate }) {
}
});
content.append(backBtn, card, actions);
content.append(backBtn, card, actions, generatedCard);
setStatus('Инициализация wallet.key...');
try {