SHA256
Миграция PDA на client.key
This commit is contained in:
@@ -21,7 +21,7 @@ export function render({ navigate }) {
|
||||
<p>Выберите, какие ключи передать на подключаемое устройство</p>
|
||||
<label class="checkbox-row"><input type="checkbox" id="connect-root" ${state.deviceConnect.root ? 'checked' : ''} /> root key</label>
|
||||
<label class="checkbox-row"><input type="checkbox" id="connect-blockchain" ${state.deviceConnect.blockchain ? 'checked' : ''} /> blockchain key</label>
|
||||
<label class="checkbox-row"><input type="checkbox" id="connect-device" checked disabled /> device key</label>
|
||||
<label class="checkbox-row"><input type="checkbox" id="connect-device" checked disabled /> client key</label>
|
||||
<p class="meta-muted" id="connect-keys-status">Проверяем ключи на этом устройстве...</p>
|
||||
<div class="row">
|
||||
<button class="icon-btn small-btn" type="button" id="tech-help">Техсправка</button>
|
||||
@@ -68,7 +68,7 @@ export function render({ navigate }) {
|
||||
<p class="meta-muted">если ключа нет — он недоступен</p>
|
||||
<p class="meta-muted">blockchain key — можно передать или нет</p>
|
||||
<p class="meta-muted">root key — только если существует</p>
|
||||
<p class="meta-muted">device key передаётся всегда</p>
|
||||
<p class="meta-muted">client key передаётся всегда</p>
|
||||
<p class="meta-muted">подключение происходит напрямую через QR</p>
|
||||
<p class="meta-muted">сервер не используется</p>
|
||||
<p class="meta-muted">текущая логика: устройство 1 показывает QR, устройство 2 сканирует</p>
|
||||
@@ -100,7 +100,7 @@ export function render({ navigate }) {
|
||||
const savedKeys = await loadEncryptedUserSecrets(state.session.login, state.session.storagePwdInMemory);
|
||||
const hasRoot = Boolean(savedKeys.rootKey);
|
||||
const hasBlockchain = Boolean(savedKeys.blockchainKey);
|
||||
const hasDevice = Boolean(savedKeys.deviceKey);
|
||||
const hasDevice = Boolean(savedKeys.clientKey);
|
||||
|
||||
rootToggle.disabled = !hasRoot;
|
||||
blockchainToggle.disabled = !hasBlockchain;
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
promptPwaInstall,
|
||||
} from '../services/pwa-install-service.js';
|
||||
import { initPwaPush } from '../services/pwa-push-service.js';
|
||||
import { getArweaveWalletFromStoredDeviceKey } from '../services/arweave-wallet-service.js';
|
||||
import { getArweaveWalletFromStoredClientKey } from '../services/arweave-wallet-service.js';
|
||||
import {
|
||||
prepareAvatarImageFile,
|
||||
uploadArweaveFile,
|
||||
@@ -143,7 +143,7 @@ function openDeveloperAvatarUploadModal({ walletLogin, storagePwd, gateway } = {
|
||||
+ `(${optimized.width}x${optimized.height}, ${optimized.contentType})`,
|
||||
);
|
||||
|
||||
walletCtx = await getArweaveWalletFromStoredDeviceKey({
|
||||
walletCtx = await getArweaveWalletFromStoredClientKey({
|
||||
login: walletLogin,
|
||||
storagePwd,
|
||||
onStatus: (message) => setMeta(message),
|
||||
|
||||
@@ -38,12 +38,12 @@ function pairingSessionKindLabel(sessionType) {
|
||||
|
||||
function buildTransferKeys(savedKeys, { withExtras = false }) {
|
||||
const keys = {
|
||||
deviceKey: String(savedKeys?.deviceKey || '').trim(),
|
||||
clientKey: String(savedKeys?.clientKey || savedKeys?.clientKey || '').trim(),
|
||||
blockchainKey: '',
|
||||
rootKey: '',
|
||||
};
|
||||
if (!keys.deviceKey) {
|
||||
throw new Error('На этом устройстве нет сохранённого device key для передачи.');
|
||||
if (!keys.clientKey) {
|
||||
throw new Error('На этом устройстве нет сохранённого client key для передачи.');
|
||||
}
|
||||
if (withExtras) {
|
||||
if (state.deviceConnect.blockchain && savedKeys?.blockchainKey) {
|
||||
@@ -432,12 +432,12 @@ export function render({ navigate }) {
|
||||
const loadSavedKeys = async () => {
|
||||
savedKeys = await loadEncryptedUserSecrets(state.session.login, state.session.storagePwdInMemory);
|
||||
const available = [];
|
||||
if (savedKeys?.deviceKey) available.push('device');
|
||||
if (savedKeys?.clientKey || savedKeys?.clientKey) available.push('client');
|
||||
if (savedKeys?.blockchainKey && state.deviceConnect.blockchain) available.push('blockchain');
|
||||
if (savedKeys?.rootKey && state.deviceConnect.root) available.push('root');
|
||||
keySummaryEl.textContent = available.length
|
||||
? `При расширенном подключении будут переданы: ${available.join(', ')}.`
|
||||
: 'На этом устройстве доступен только device key.';
|
||||
: 'На этом устройстве доступен только client key.';
|
||||
};
|
||||
|
||||
const reloadRequests = async ({ silent = false } = {}) => {
|
||||
@@ -464,9 +464,9 @@ export function render({ navigate }) {
|
||||
const withExtras = mode === 'with-extras';
|
||||
let payload;
|
||||
if (!withExtras && Number(request?.requesterSessionType || 0) === SESSION_TYPE_WALLET) {
|
||||
const delegatedSession = await authService.createDelegatedSessionWithDeviceKey({
|
||||
const delegatedSession = await authService.createDelegatedSessionWithClientKey({
|
||||
login: state.session.login,
|
||||
devicePrivPkcs8: String(savedKeys?.deviceKey || '').trim(),
|
||||
clientPrivPkcs8: String(savedKeys?.clientKey || savedKeys?.clientKey || '').trim(),
|
||||
sessionKey: String(request?.requesterSessionKey || '').trim(),
|
||||
sessionType: Number(request?.requesterSessionType || SESSION_TYPE_WALLET) || SESSION_TYPE_WALLET,
|
||||
clientPlatform: String(request?.requesterClientPlatform || '').trim() || 'Wallet plugin',
|
||||
|
||||
@@ -42,11 +42,11 @@ export function render({ navigate }) {
|
||||
}
|
||||
const savedKeys = await loadEncryptedUserSecrets(state.session.login, state.session.storagePwdInMemory);
|
||||
const keys = {
|
||||
deviceKey: savedKeys.deviceKey || '',
|
||||
clientKey: savedKeys.clientKey || '',
|
||||
blockchainKey: state.deviceConnect.blockchain ? (savedKeys.blockchainKey || '') : '',
|
||||
rootKey: state.deviceConnect.root ? (savedKeys.rootKey || '') : '',
|
||||
};
|
||||
if (!keys.deviceKey) throw new Error('На этом устройстве нет device key');
|
||||
if (!keys.clientKey) throw new Error('На этом устройстве нет client key');
|
||||
|
||||
const qrText = makeKeyTransferText({ login: state.session.login, keys });
|
||||
qrEl.innerHTML = renderQrSvg(qrText);
|
||||
|
||||
@@ -43,7 +43,7 @@ export function render({ navigate }) {
|
||||
</div>
|
||||
<div class="key-card stack">
|
||||
<label class="checkbox-row"><span class="field-label">Device Key</span></label>
|
||||
<input class="input" type="text" value="${state.keyStorage.deviceKey}" />
|
||||
<input class="input" type="text" value="${state.keyStorage.clientKey}" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -63,7 +63,7 @@ export function render({ navigate }) {
|
||||
|
||||
const deviceInput = card.children[2].querySelector('.input');
|
||||
deviceInput.addEventListener('input', () => {
|
||||
state.keyStorage.deviceKey = deviceInput.value;
|
||||
state.keyStorage.clientKey = deviceInput.value;
|
||||
});
|
||||
|
||||
const actions = document.createElement('div');
|
||||
|
||||
@@ -141,8 +141,8 @@ export function render({ navigate }) {
|
||||
const parseTransferText = (text) => {
|
||||
try {
|
||||
const transfer = parseKeyTransferText(text);
|
||||
if (!transfer.keys.deviceKey) {
|
||||
throw new Error('В QR-коде нет device key для входа');
|
||||
if (!transfer.keys.clientKey) {
|
||||
throw new Error('В QR-коде нет client key для входа');
|
||||
}
|
||||
showTransfer(transfer);
|
||||
} catch (error) {
|
||||
|
||||
@@ -379,7 +379,7 @@ export function render({ navigate }) {
|
||||
|
||||
const details2 = document.createElement('p');
|
||||
details2.className = 'meta-muted';
|
||||
details2.textContent = 'Из этого секрета строятся root key, blockchain key и device key. Это может занять некоторое время.';
|
||||
details2.textContent = 'Из этого секрета строятся recovery key, root key, blockchain key и client key. Это может занять некоторое время.';
|
||||
|
||||
const details3 = document.createElement('p');
|
||||
details3.className = 'meta-muted';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { renderHeader } from '../components/header.js';
|
||||
import { state } from '../state.js';
|
||||
import { base64ToBytes, bytesToBase58 } from '../services/crypto-utils.js';
|
||||
import { extractSeed32FromPkcs8B64 } from '../services/device-key-utils.js';
|
||||
import { extractSeed32FromPkcs8B64 } from '../services/client-key-utils.js';
|
||||
|
||||
export const pageMeta = { id: 'registration-draft-keys-view', title: 'Сгенерированные ключи', showAppChrome: false };
|
||||
|
||||
@@ -109,6 +109,20 @@ export function render({ navigate }) {
|
||||
}
|
||||
card.append(makeSecretField({ label: 'Главный секрет (master secret, base58, 32 байта)', value: secretB58 }));
|
||||
|
||||
// Recovery key
|
||||
const recoverySep = document.createElement('p');
|
||||
recoverySep.className = 'field-label';
|
||||
recoverySep.textContent = 'Recovery key';
|
||||
card.append(recoverySep);
|
||||
card.append(makePublicField({
|
||||
label: 'Recovery — публичный (base58)',
|
||||
value: bytesToBase58(base64ToBytes(keyBundle.recoveryPair.publicKeyB64)),
|
||||
}));
|
||||
card.append(makeSecretField({
|
||||
label: 'Recovery — приватный (seed base58, 32 байта)',
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.recoveryPair.privatePkcs8B64)),
|
||||
}));
|
||||
|
||||
// Root key
|
||||
const rootSep = document.createElement('p');
|
||||
rootSep.className = 'field-label';
|
||||
@@ -137,18 +151,18 @@ export function render({ navigate }) {
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.blockchainPair.privatePkcs8B64)),
|
||||
}));
|
||||
|
||||
// Device key
|
||||
// Client key
|
||||
const devSep = document.createElement('p');
|
||||
devSep.className = 'field-label';
|
||||
devSep.textContent = 'Device key (= Solana wallet)';
|
||||
devSep.textContent = 'Client key (= Solana wallet)';
|
||||
card.append(devSep);
|
||||
card.append(makePublicField({
|
||||
label: 'Device — публичный (base58)',
|
||||
value: bytesToBase58(base64ToBytes(keyBundle.devicePair.publicKeyB64)),
|
||||
label: 'Client — публичный (base58)',
|
||||
value: bytesToBase58(base64ToBytes(keyBundle.clientPair.publicKeyB64)),
|
||||
}));
|
||||
card.append(makeSecretField({
|
||||
label: 'Device — приватный (seed base58, 32 байта)',
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.devicePair.privatePkcs8B64)),
|
||||
label: 'Client — приватный (seed base58, 32 байта)',
|
||||
value: bytesToBase58(extractSeed32FromPkcs8B64(keyBundle.clientPair.privatePkcs8B64)),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const REGISTRATION_FAQ_TOPICS = [
|
||||
title: 'Как генерируются ключи и что делает пароль?',
|
||||
paragraphs: [
|
||||
'Из вашего логина и пароля с помощью Argon2id вычисляется специальный секрет.',
|
||||
'Уже из этого секрета детерминированно строятся три основных ключа: root key, blockchain key и device key.',
|
||||
'Уже из этого секрета детерминированно строятся четыре основных ключа: recovery key, root key, blockchain key и client key.',
|
||||
'Это значит, что логин и пароль не просто проверяются на сервере, а реально участвуют в создании ваших ключей. У разных логинов даже с одинаковым паролем будут разные ключи.',
|
||||
],
|
||||
},
|
||||
@@ -41,7 +41,7 @@ export const REGISTRATION_FAQ_TOPICS = [
|
||||
paragraphs: [
|
||||
'Root key нужен для управления вашей основной публичной записью и важными изменениями личности, включая обновление главной публичной части в Solana.',
|
||||
'Blockchain key нужен для подписания действий и записей в блокчейне SHiNE.',
|
||||
'Device key нужен для входов и работы конкретного устройства. Благодаря разделению ключей можно точнее выдавать права одним устройствам и не выдавать другим.',
|
||||
'Client key нужен для входов и работы конкретного устройства. Благодаря разделению ключей можно точнее выдавать права одним устройствам и не выдавать другим.',
|
||||
'Если не хочется в это вникать, обычно можно просто сохранить все ключи на своём устройстве. Для большинства обычных сценариев на iPhone, Android и Linux это вполне практично. Для больших сумм или повышенного риска лучше отдельное внешнее устройство.',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ export function render({ navigate }) {
|
||||
const deriveUserWalletAddress = async () => {
|
||||
const keyBundle = state.registrationDraft.preGeneratedKeyBundle;
|
||||
if (!keyBundle) throw new Error('Ключи ещё не сгенерированы. Вернитесь на предыдущий шаг.');
|
||||
const { publicKeyB64 } = keyBundle.devicePair;
|
||||
const { publicKeyB64 } = keyBundle.clientPair;
|
||||
const raw = atob(publicKeyB64);
|
||||
const bytes = new Uint8Array(raw.length);
|
||||
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
|
||||
@@ -219,7 +219,7 @@ export function render({ navigate }) {
|
||||
|
||||
card.innerHTML = `
|
||||
<p class="auth-copy">Для регистрации в тестовой Solana нужно минимум 0,01 SOL на вашем кошельке.</p>
|
||||
<label class="stack"><span class="field-label">Номер кошелька (wallet.key = device.key)</span></label>
|
||||
<label class="stack"><span class="field-label">Номер кошелька (client.key)</span></label>
|
||||
<div class="stack">
|
||||
<span class="field-label">Баланс (Solana)</span>
|
||||
</div>
|
||||
@@ -242,7 +242,7 @@ export function render({ navigate }) {
|
||||
await refreshBalance({ addressOverride: walletAddress });
|
||||
} catch (error) {
|
||||
status.className = 'status-line is-unavailable';
|
||||
status.textContent = `Не удалось подготовить wallet.key: ${error?.message || 'unknown'}`;
|
||||
status.textContent = `Не удалось подготовить client.key: ${error?.message || 'unknown'}`;
|
||||
status.style.display = '';
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { renderHeader } from '../components/header.js';
|
||||
import { state } from '../state.js';
|
||||
import { bytesToBase58 } from '../services/crypto-utils.js';
|
||||
import { extractSeed32FromPkcs8B64 } from '../services/device-key-utils.js';
|
||||
import { extractSeed32FromPkcs8B64 } from '../services/client-key-utils.js';
|
||||
import { loadEncryptedUserSecrets } from '../services/key-vault.js';
|
||||
|
||||
export const pageMeta = { id: 'show-keys-view', title: 'Показать ключи' };
|
||||
@@ -68,7 +68,7 @@ export function render({ navigate }) {
|
||||
card.append(
|
||||
renderField('root', 'root key (base58)'),
|
||||
renderField('blockchain', 'blockchain.key (base58)'),
|
||||
renderField('device', 'device key (base58)'),
|
||||
renderField('device', 'client key (base58)'),
|
||||
);
|
||||
|
||||
const setMissingState = (id) => {
|
||||
@@ -133,11 +133,11 @@ export function render({ navigate }) {
|
||||
|
||||
const rootSeed32 = savedKeys.rootKey ? extractSeed32FromPkcs8B64(savedKeys.rootKey) : null;
|
||||
const blockchainSeed32 = savedKeys.blockchainKey ? extractSeed32FromPkcs8B64(savedKeys.blockchainKey) : null;
|
||||
const deviceSeed32 = savedKeys.deviceKey ? extractSeed32FromPkcs8B64(savedKeys.deviceKey) : null;
|
||||
const clientSeed32 = savedKeys.clientKey ? extractSeed32FromPkcs8B64(savedKeys.clientKey) : null;
|
||||
|
||||
keys.root = rootSeed32 ? bytesToBase58(rootSeed32) : '';
|
||||
keys.blockchain = blockchainSeed32 ? bytesToBase58(blockchainSeed32) : '';
|
||||
keys.device = deviceSeed32 ? bytesToBase58(deviceSeed32) : '';
|
||||
keys.device = clientSeed32 ? bytesToBase58(clientSeed32) : '';
|
||||
|
||||
if (keys.root || keys.blockchain || keys.device) {
|
||||
status.textContent = 'Показаны только ключи, сохранённые на этом устройстве.';
|
||||
|
||||
@@ -12,12 +12,12 @@ export const pageMeta = { id: 'topup-view', title: 'Пополнение сче
|
||||
// Канонический Solana-адрес пополнения = публичный device-ключ из сгенерированного набора ключей.
|
||||
// Тот же путь, что в registration-payment-view (deriveUserWalletAddress); не выводим адрес
|
||||
// напрямую из пароля, иначе он расходится с device-ключом регистрации.
|
||||
async function deviceWalletAddressFromBundle() {
|
||||
async function clientWalletAddressFromBundle() {
|
||||
const keyBundle = state.registrationDraft.preGeneratedKeyBundle;
|
||||
if (!keyBundle || !keyBundle.devicePair) {
|
||||
if (!keyBundle || !keyBundle.clientPair) {
|
||||
throw new Error('Ключи ещё не сгенерированы. Вернитесь на экран регистрации.');
|
||||
}
|
||||
const raw = atob(keyBundle.devicePair.publicKeyB64);
|
||||
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 import('https://esm.sh/@solana/web3.js@1.98.4');
|
||||
@@ -69,7 +69,7 @@ export function render({ navigate }) {
|
||||
</div>
|
||||
<a class="link-card" id="topup-site-link" href="${getTopupSiteUrl(state.registrationPayment.walletAddress || '')}" target="_blank" rel="noreferrer">Открыть сайт пополнения</a>
|
||||
<div class="card stack" style="padding:12px; max-width:320px;">
|
||||
<div class="field-label" style="margin-bottom:6px;">Кошелёк для пополнения (device key = Solana wallet)</div>
|
||||
<div class="field-label" style="margin-bottom:6px;">Кошелёк для пополнения (client key = Solana wallet)</div>
|
||||
</div>
|
||||
`;
|
||||
card.children[3].append(walletRow);
|
||||
@@ -117,7 +117,7 @@ export function render({ navigate }) {
|
||||
(async () => {
|
||||
try {
|
||||
if (!walletValue.value) {
|
||||
const address = await deviceWalletAddressFromBundle();
|
||||
const address = await clientWalletAddressFromBundle();
|
||||
state.registrationPayment.walletAddress = address;
|
||||
walletValue.value = address;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import {
|
||||
formatSol,
|
||||
getBalanceSol,
|
||||
getTopupSiteUrl,
|
||||
getWalletFromStoredDeviceKey,
|
||||
getWalletFromStoredClientKey,
|
||||
transferSol,
|
||||
} from '../services/solana-wallet-service.js';
|
||||
import {
|
||||
formatAr,
|
||||
getArweaveBalance,
|
||||
getArweaveTopupSiteUrl,
|
||||
getArweaveWalletFromStoredDeviceKey,
|
||||
getArweaveWalletFromStoredClientKey,
|
||||
transferAr,
|
||||
} from '../services/arweave-wallet-service.js';
|
||||
import { loadEncryptedUserSecrets } from '../services/key-vault.js';
|
||||
@@ -262,10 +262,10 @@ export function render({ navigate }) {
|
||||
}
|
||||
let rootKey = String(saved?.rootKey || '').trim();
|
||||
let blockchainKey = String(saved?.blockchainKey || '').trim();
|
||||
const deviceKey = String(saved?.deviceKey || '').trim();
|
||||
if (!deviceKey) throw new Error('На устройстве нет device.key. Выполните вход заново.');
|
||||
const clientKey = String(saved?.clientKey || '').trim();
|
||||
if (!clientKey) throw new Error('На устройстве нет client.key. Выполните вход заново.');
|
||||
if (rootKey && blockchainKey) {
|
||||
return { rootPrivatePkcs8B64: rootKey, blockchainPrivatePkcs8B64: blockchainKey, devicePrivatePkcs8B64: deviceKey };
|
||||
return { rootPrivatePkcs8B64: rootKey, blockchainPrivatePkcs8B64: blockchainKey, clientPrivatePkcs8B64: clientKey };
|
||||
}
|
||||
|
||||
const password = window.prompt(
|
||||
@@ -284,7 +284,7 @@ export function render({ navigate }) {
|
||||
if (shouldSave) {
|
||||
await authService.persistSelectedKeys(login, storagePwd, keyBundle, { saveRoot: true, saveBlockchain: true });
|
||||
}
|
||||
return { rootPrivatePkcs8B64: rootKey, blockchainPrivatePkcs8B64: blockchainKey, devicePrivatePkcs8B64: deviceKey };
|
||||
return { rootPrivatePkcs8B64: rootKey, blockchainPrivatePkcs8B64: blockchainKey, clientPrivatePkcs8B64: clientKey };
|
||||
};
|
||||
|
||||
const setButtonsDisabled = (disabled) => {
|
||||
@@ -341,7 +341,7 @@ export function render({ navigate }) {
|
||||
solanaEndpoint: state.entrySettings.solanaServer,
|
||||
rootPrivatePkcs8B64: signing.rootPrivatePkcs8B64,
|
||||
blockchainPrivatePkcs8B64: signing.blockchainPrivatePkcs8B64,
|
||||
devicePrivatePkcs8B64: signing.devicePrivatePkcs8B64,
|
||||
clientPrivatePkcs8B64: signing.clientPrivatePkcs8B64,
|
||||
additionalLimitBytes: 0n,
|
||||
nextUsedBytes: BigInt(Math.max(0, serverState.sizeBytes)),
|
||||
nextLastBlockNumber: serverState.lastNumber,
|
||||
@@ -388,7 +388,7 @@ export function render({ navigate }) {
|
||||
solanaEndpoint: state.entrySettings.solanaServer,
|
||||
rootPrivatePkcs8B64: signing.rootPrivatePkcs8B64,
|
||||
blockchainPrivatePkcs8B64: signing.blockchainPrivatePkcs8B64,
|
||||
devicePrivatePkcs8B64: signing.devicePrivatePkcs8B64,
|
||||
clientPrivatePkcs8B64: signing.clientPrivatePkcs8B64,
|
||||
additionalLimitBytes: addBytes,
|
||||
});
|
||||
if (modeToken !== activeModeToken) return;
|
||||
@@ -443,7 +443,7 @@ export function render({ navigate }) {
|
||||
addressCard.className = 'card';
|
||||
addressCard.style.padding = '10px';
|
||||
addressCard.innerHTML = `
|
||||
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес (wallet.key = device.key)</p>
|
||||
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес (client.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');
|
||||
@@ -705,7 +705,7 @@ export function render({ navigate }) {
|
||||
|
||||
sendBtn.addEventListener('click', async () => {
|
||||
if (!walletCtx?.keypair) {
|
||||
setStatus('Перевод недоступен: wallet.key не загружен.');
|
||||
setStatus('Перевод недоступен: client.key не загружен.');
|
||||
return;
|
||||
}
|
||||
const toAddress = window.prompt('Введите адрес получателя (Solana):', '');
|
||||
@@ -741,17 +741,17 @@ export function render({ navigate }) {
|
||||
});
|
||||
|
||||
content.append(backBtn, card, actions, generatedCard);
|
||||
setStatus('Инициализация wallet.key...');
|
||||
setStatus('Инициализация client.key...');
|
||||
|
||||
try {
|
||||
walletCtx = await getWalletFromStoredDeviceKey(sessionArgsOrThrow());
|
||||
walletCtx = await getWalletFromStoredClientKey(sessionArgsOrThrow());
|
||||
if (modeToken !== activeModeToken) return;
|
||||
walletAddress = walletCtx.address;
|
||||
addressEl.textContent = walletAddress;
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
addressEl.textContent = 'wallet.key недоступен';
|
||||
addressEl.textContent = 'client.key недоступен';
|
||||
setStatus(`Не удалось инициализировать кошелёк: ${error?.message || 'unknown'}`);
|
||||
}
|
||||
}
|
||||
@@ -797,7 +797,7 @@ export function render({ navigate }) {
|
||||
helpCard.innerHTML = `
|
||||
<summary style="cursor:pointer; font-weight:600;">Как получен этот адрес?</summary>
|
||||
<p class="meta-muted" style="margin-top:8px;">
|
||||
SHiNE берёт ваш локальный device.key и по стандарту SAWD-v1 получает из него нативный Arweave-кошелёк.
|
||||
SHiNE берёт ваш локальный client.key и по стандарту SAWD-v1 получает из него нативный Arweave-кошелёк.
|
||||
Приватный ключ не отправляется на сервер. После первого расчёта он хранится только в зашифрованном контейнере этого устройства.
|
||||
</p>
|
||||
`;
|
||||
@@ -899,14 +899,14 @@ export function render({ navigate }) {
|
||||
|
||||
try {
|
||||
let wasFirstTimeGeneration = false;
|
||||
arweaveWalletCtx = await getArweaveWalletFromStoredDeviceKey({
|
||||
arweaveWalletCtx = await getArweaveWalletFromStoredClientKey({
|
||||
...sessionArgsOrThrow(),
|
||||
onStatus: (message) => {
|
||||
const text = String(message || '').trim();
|
||||
if (!text) return;
|
||||
if (text.includes('впервые получаем Arweave-кошелёк')) {
|
||||
wasFirstTimeGeneration = true;
|
||||
setStatus('Подождите — ваш Arweave-ключ вычисляется из device key. Это происходит только один раз, потом будет мгновенно.');
|
||||
setStatus('Подождите — ваш Arweave-ключ вычисляется из client key. Это происходит только один раз, потом будет мгновенно.');
|
||||
return;
|
||||
}
|
||||
setStatus(text);
|
||||
@@ -919,7 +919,7 @@ export function render({ navigate }) {
|
||||
await refreshBalance();
|
||||
} catch (error) {
|
||||
if (modeToken !== activeModeToken) return;
|
||||
addressEl.textContent = 'wallet.key недоступен';
|
||||
addressEl.textContent = 'client.key недоступен';
|
||||
clearArweaveSecretsInMemory();
|
||||
setStatus(`Не удалось инициализировать Arweave-кошелёк: ${error?.message || 'unknown'}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user