SHA256
Добавить SAWD-v1 и Arweave-кошелек в UI
This commit is contained in:
+392
-162
@@ -8,6 +8,13 @@ import {
|
||||
requestAirdropSol,
|
||||
transferSol,
|
||||
} from '../services/solana-wallet-service.js';
|
||||
import {
|
||||
formatAr,
|
||||
getArweaveBalance,
|
||||
getArweaveTopupSiteUrl,
|
||||
getArweaveWalletFromStoredDeviceKey,
|
||||
transferAr,
|
||||
} from '../services/arweave-wallet-service.js';
|
||||
|
||||
export const pageMeta = { id: 'wallet-view', title: 'Кошелёк' };
|
||||
|
||||
@@ -15,186 +22,409 @@ function nowRu() {
|
||||
return new Date().toLocaleString('ru-RU');
|
||||
}
|
||||
|
||||
function createModeBackButton(renderWalletChoice) {
|
||||
const backBtn = document.createElement('button');
|
||||
backBtn.className = 'text-btn';
|
||||
backBtn.textContent = '← К выбору кошелька';
|
||||
backBtn.addEventListener('click', () => {
|
||||
renderWalletChoice();
|
||||
});
|
||||
return backBtn;
|
||||
}
|
||||
|
||||
function sessionArgsOrThrow() {
|
||||
const login = String(state.session.login || '').trim();
|
||||
const storagePwd = String(state.session.storagePwdInMemory || '').trim();
|
||||
if (!login || !storagePwd) {
|
||||
throw new Error('Нет активной сессии. Выполните вход заново.');
|
||||
}
|
||||
return { login, storagePwd };
|
||||
}
|
||||
|
||||
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 = `
|
||||
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес (wallet.key = device.key)</p>
|
||||
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">—</p>
|
||||
`;
|
||||
|
||||
card.append(balanceWrap, addressCard);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'stack';
|
||||
actions.innerHTML = `
|
||||
<div class="row">
|
||||
<button class="text-btn" id="copy-address">Копировать адрес</button>
|
||||
<button class="ghost-btn" id="refresh-balance">Обновить баланс</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="primary-btn" id="send-sol" style="width:100%;">Перевести</button>
|
||||
<button class="primary-btn" id="topup-sol" style="width:100%;">Пополнить</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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;
|
||||
const content = document.createElement('div');
|
||||
content.className = 'stack';
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Кошелёк',
|
||||
leftAction: { label: '←', onClick: () => navigate('profile-view') },
|
||||
}),
|
||||
content,
|
||||
status,
|
||||
);
|
||||
|
||||
let activeModeToken = 0;
|
||||
let arweaveWalletCtx = null;
|
||||
|
||||
function clearArweaveSecretsInMemory() {
|
||||
if (!arweaveWalletCtx?.jwk) return;
|
||||
Object.keys(arweaveWalletCtx.jwk).forEach((key) => {
|
||||
arweaveWalletCtx.jwk[key] = '';
|
||||
});
|
||||
arweaveWalletCtx.jwk = null;
|
||||
arweaveWalletCtx = null;
|
||||
}
|
||||
|
||||
function renderWalletChoice() {
|
||||
activeModeToken += 1;
|
||||
clearArweaveSecretsInMemory();
|
||||
content.innerHTML = '';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card stack';
|
||||
card.innerHTML = `
|
||||
<h2 style="margin:0 0 6px;">Кошелёк</h2>
|
||||
<p class="meta-muted">Выберите режим кошелька.</p>
|
||||
`;
|
||||
|
||||
const solanaBtn = document.createElement('button');
|
||||
solanaBtn.className = 'primary-btn';
|
||||
solanaBtn.style.width = '100%';
|
||||
solanaBtn.textContent = 'Solana кошелёк';
|
||||
solanaBtn.addEventListener('click', () => {
|
||||
void renderSolanaWallet();
|
||||
});
|
||||
|
||||
const arweaveBtn = document.createElement('button');
|
||||
arweaveBtn.className = 'primary-btn';
|
||||
arweaveBtn.style.width = '100%';
|
||||
arweaveBtn.textContent = 'Arweave кошелёк';
|
||||
arweaveBtn.addEventListener('click', () => {
|
||||
void renderArweaveWallet();
|
||||
});
|
||||
|
||||
card.append(solanaBtn, arweaveBtn);
|
||||
content.append(card);
|
||||
setStatus('Выберите тип кошелька.');
|
||||
}
|
||||
|
||||
async function renderSolanaWallet() {
|
||||
const modeToken = ++activeModeToken;
|
||||
clearArweaveSecretsInMemory();
|
||||
content.innerHTML = '';
|
||||
|
||||
let walletCtx = null;
|
||||
let walletAddress = '';
|
||||
|
||||
const backBtn = createModeBackButton(renderWalletChoice);
|
||||
|
||||
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 = `
|
||||
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес (wallet.key = device.key)</p>
|
||||
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">—</p>
|
||||
`;
|
||||
const addressEl = addressCard.querySelector('#wallet-address-value');
|
||||
|
||||
card.append(balanceWrap, addressCard);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'stack';
|
||||
actions.innerHTML = `
|
||||
<div class="row">
|
||||
<button class="text-btn" id="copy-address">Копировать адрес</button>
|
||||
<button class="ghost-btn" id="refresh-balance">Обновить баланс</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="primary-btn" id="send-sol" style="width:100%;">Перевести</button>
|
||||
<button class="primary-btn" id="topup-sol" style="width:100%;">Пополнить</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 refreshBalance = async () => {
|
||||
if (!walletAddress) {
|
||||
setStatus('Кошелёк не инициализирован.');
|
||||
return;
|
||||
}
|
||||
refreshBtn.disabled = true;
|
||||
try {
|
||||
const balance = await getBalanceSol({
|
||||
endpoint: state.entrySettings.solanaServer,
|
||||
address: walletAddress,
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
balanceValue.textContent = `${formatSol(balance.sol, 6)} SOL`;
|
||||
updatedLabel.textContent = `Обновлено: ${nowRu()}`;
|
||||
endpointLabel.textContent = `RPC: ${balance.endpoint}`;
|
||||
setStatus('Баланс обновлён.');
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
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(',', '.')),
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
setStatus(`Перевод отправлен. Signature: ${tx.signature}`);
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
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,
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
setStatus(`Тестовое пополнение выполнено (airdrop 1 SOL). Signature: ${drop.signature}`);
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
setStatus(`Ошибка тестового пополнения: ${error?.message || 'unknown'}`);
|
||||
} finally {
|
||||
topupBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
content.append(backBtn, card, actions);
|
||||
setStatus('Инициализация wallet.key...');
|
||||
|
||||
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,
|
||||
});
|
||||
walletCtx = await getWalletFromStoredDeviceKey(sessionArgsOrThrow());
|
||||
if (modeToken !== activeModeToken) return;
|
||||
walletAddress = walletCtx.address;
|
||||
addressEl.textContent = walletAddress;
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
addressEl.textContent = 'wallet.key недоступен';
|
||||
setStatus(`Не удалось инициализировать кошелёк: ${error?.message || 'unknown'}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
screen.append(card, actions, status);
|
||||
async function renderArweaveWallet() {
|
||||
const modeToken = ++activeModeToken;
|
||||
content.innerHTML = '';
|
||||
|
||||
let walletAddress = '';
|
||||
|
||||
const backBtn = createModeBackButton(renderWalletChoice);
|
||||
|
||||
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 = 'Баланс (Arweave)';
|
||||
const balanceValue = document.createElement('h2');
|
||||
balanceValue.style.fontSize = '30px';
|
||||
balanceValue.textContent = '— AR';
|
||||
const updatedLabel = document.createElement('p');
|
||||
updatedLabel.className = 'meta-muted';
|
||||
updatedLabel.textContent = 'Обновлено: —';
|
||||
const gatewayLabel = document.createElement('p');
|
||||
gatewayLabel.className = 'meta-muted';
|
||||
gatewayLabel.textContent = `Gateway: ${state.entrySettings.arweaveServer}`;
|
||||
balanceWrap.append(balanceLabel, balanceValue, updatedLabel, gatewayLabel);
|
||||
|
||||
const addressCard = document.createElement('div');
|
||||
addressCard.className = 'card';
|
||||
addressCard.style.padding = '10px';
|
||||
addressCard.innerHTML = `
|
||||
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес Arweave (SAWD-v1)</p>
|
||||
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">—</p>
|
||||
`;
|
||||
const addressEl = addressCard.querySelector('#wallet-address-value');
|
||||
|
||||
card.append(balanceWrap, addressCard);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'stack';
|
||||
actions.innerHTML = `
|
||||
<div class="row">
|
||||
<button class="text-btn" id="copy-address">Копировать адрес</button>
|
||||
<button class="ghost-btn" id="refresh-balance">Обновить баланс</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="primary-btn" id="send-ar" style="width:100%;">Перевести</button>
|
||||
<button class="primary-btn" id="topup-ar" style="width:100%;">Пополнить</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const copyBtn = actions.querySelector('#copy-address');
|
||||
const refreshBtn = actions.querySelector('#refresh-balance');
|
||||
const sendBtn = actions.querySelector('#send-ar');
|
||||
const topupBtn = actions.querySelector('#topup-ar');
|
||||
|
||||
const refreshBalance = async () => {
|
||||
if (!walletAddress) {
|
||||
setStatus('Arweave-кошелёк не инициализирован.');
|
||||
return;
|
||||
}
|
||||
refreshBtn.disabled = true;
|
||||
try {
|
||||
const balance = await getArweaveBalance({
|
||||
gateway: state.entrySettings.arweaveServer,
|
||||
address: walletAddress,
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
balanceValue.textContent = `${formatAr(balance.ar, 6)} AR`;
|
||||
updatedLabel.textContent = `Обновлено: ${nowRu()}`;
|
||||
gatewayLabel.textContent = `Gateway: ${balance.gateway}`;
|
||||
setStatus('Баланс обновлён.');
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
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 (!arweaveWalletCtx?.jwk) {
|
||||
setStatus('Перевод недоступен: Arweave-кошелёк не инициализирован.');
|
||||
return;
|
||||
}
|
||||
const toAddress = window.prompt('Введите адрес получателя (Arweave):', '');
|
||||
if (!toAddress) return;
|
||||
const amountRaw = window.prompt('Введите сумму AR для перевода:', '0.01');
|
||||
if (!amountRaw) return;
|
||||
|
||||
sendBtn.disabled = true;
|
||||
try {
|
||||
const tx = await transferAr({
|
||||
gateway: state.entrySettings.arweaveServer,
|
||||
jwk: arweaveWalletCtx.jwk,
|
||||
toAddress,
|
||||
amountAr: amountRaw,
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
setStatus(`Перевод отправлен. Transaction ID: ${tx.id}`);
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
setStatus(`Ошибка перевода: ${error?.message || 'unknown'}`);
|
||||
} finally {
|
||||
sendBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
topupBtn.addEventListener('click', () => {
|
||||
window.open(getArweaveTopupSiteUrl(), '_blank', 'noopener,noreferrer');
|
||||
setStatus('Открыта страница пополнения.');
|
||||
});
|
||||
|
||||
content.append(backBtn, card, actions);
|
||||
setStatus('Генерация Arweave-кошелька...');
|
||||
|
||||
try {
|
||||
arweaveWalletCtx = await getArweaveWalletFromStoredDeviceKey(sessionArgsOrThrow());
|
||||
if (modeToken !== activeModeToken) return;
|
||||
walletAddress = arweaveWalletCtx.address;
|
||||
addressEl.textContent = walletAddress;
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
addressEl.textContent = 'wallet.key недоступен';
|
||||
clearArweaveSecretsInMemory();
|
||||
setStatus(`Не удалось инициализировать Arweave-кошелёк: ${error?.message || 'unknown'}`);
|
||||
}
|
||||
}
|
||||
|
||||
renderWalletChoice();
|
||||
return screen;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user