SHA256
Обновление архитектуры ключей и кошельков
This commit is contained in:
@@ -3,6 +3,7 @@ import { authService, state } from '../state.js';
|
||||
import { bytesToBase64 } from '../services/crypto-utils.js';
|
||||
import { readShineUserPda } from '../services/shine-user-pda-service.js';
|
||||
import { KeyRotationClient, KEY_ROTATION_REASONS } from '../services/key-rotation-service.js';
|
||||
import { formatSol, getBalanceSol, getSolanaWalletFromStoredSecret, transferAllSol } from '../services/solana-wallet-service.js';
|
||||
|
||||
export const pageMeta = { id:'key-rotation-view', title:'Смена ключей', hideToolbar:true };
|
||||
|
||||
@@ -56,12 +57,66 @@ export function render({navigate,chrome}){
|
||||
|
||||
async function renderChainReady(s){
|
||||
const oldP=input('Текущий пароль');const newP=input('Новый пароль');const go=document.createElement('button');go.className='primary-btn';go.textContent='Изменить ключи в Solana';const abort=document.createElement('button');abort.className='secondary-btn';abort.textContent='Прервать смену ключей';const err=document.createElement('div');err.className='meta-muted';
|
||||
go.addEventListener('click',async()=>{go.disabled=true;try{cachedOldBundle=await derive(oldP.el.value);cachedNewBundle=await derive(newP.el.value);await client.rotatePda({login,solanaEndpoint,oldBundle:cachedOldBundle,newBundle:cachedNewBundle,storagePwd});await refresh();}catch(e){err.textContent=e?.message||String(e);go.disabled=false;}});abort.addEventListener('click',async()=>{await client.abort();await refresh();});setRoot(statusCard(s),note('Новая цепочка полностью готова. Следующий шаг — точка невозврата: старый root разрешит атомарную смену root + client + blockchain fork, а новое PDA подпишет новый blockchain key.'),oldP.wrap,newP.wrap,go,abort,err);
|
||||
go.addEventListener('click',async()=>{go.disabled=true;try{cachedOldBundle=await derive(oldP.el.value);cachedNewBundle=await derive(newP.el.value);await client.rotatePda({login,solanaEndpoint,oldBundle:cachedOldBundle,newBundle:cachedNewBundle,storagePwd});await refresh();}catch(e){err.textContent=e?.message||String(e);go.disabled=false;}});abort.addEventListener('click',async()=>{await client.abort();await refresh();});setRoot(statusCard(s),note('Новая цепочка полностью готова. Следующий шаг — точка невозврата: старый ключ восстановления временно используется для атомарной смены recovery + client + blockchain fork, а новое PDA подпишет новый blockchain key. Приватный ключ восстановления на устройстве не сохраняется.'),oldP.wrap,newP.wrap,go,abort,err);
|
||||
}
|
||||
|
||||
function pollView(s){setRoot(statusCard(s),note(s.rotationStatus==='REBUILDING_SERVER'?'Сервер перестраивает текущую рабочую историю по новому fork.':'Ожидаем подтверждение нового состояния PDA в Solana.'));timer=setTimeout(()=>void refresh(),POLL_MS);}
|
||||
function placeholderView(s,kind){const wallet=kind==='wallet';const text=wallet?'Перевод SOL со старого blockchain-wallet на новый пока не реализован. Этот этап сейчас будет отмечен как NOT_IMPLEMENTED и пропущен.':'Перешифрование старых личных сообщений новым client key пока не реализовано. Этот этап сейчас будет отмечен как NOT_IMPLEMENTED и пропущен.';const go=document.createElement('button');go.className='primary-btn';go.textContent='Продолжить';go.addEventListener('click',async()=>{go.disabled=true;try{const next=await client.continuePlaceholder();if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}await refresh();}catch(e){go.disabled=false;root.append(note(e?.message||String(e)));}});setRoot(statusCard(s),note(text),go);}
|
||||
async function renderWalletMigration(s){
|
||||
const oldP=cachedOldBundle?null:input('Старый пароль');
|
||||
const newP=cachedNewBundle?null:input('Новый пароль');
|
||||
const info=note('После смены blockchain key меняется и Solana-кошелёк. На этом шаге можно перевести весь доступный SOL со старого кошелька на новый за вычетом сетевой комиссии.');
|
||||
const warning=note('Сейчас автоматически переносится только SOL. Миграцию AR/Turbo при смене blockchain key нужно доработать отдельно.');
|
||||
const details=document.createElement('div');details.className='card stack';details.innerHTML='<span class="meta-muted">Проверьте старый и новый кошельки.</span>';
|
||||
const prepare=document.createElement('button');prepare.className='secondary-btn';prepare.type='button';prepare.textContent='Проверить кошельки';
|
||||
const transfer=document.createElement('button');transfer.className='primary-btn';transfer.type='button';transfer.textContent='Перевести SOL и продолжить';transfer.disabled=true;
|
||||
const skip=document.createElement('button');skip.className='secondary-btn';skip.type='button';skip.textContent='Пропустить перенос';
|
||||
const err=document.createElement('div');err.className='meta-muted';
|
||||
let oldWallet=null,newWallet=null,oldBalance=null;
|
||||
|
||||
async function refresh(){if(disposed)return;if(timer){clearTimeout(timer);timer=null;}try{const s=await client.status();const st=String(s.rotationStatus||'NONE');if(st==='NONE'){await renderNone();return;}if(st==='COPYING_CHAIN'){if(cachedNewBundle)await runCopy(s);else await askNewPasswordAndCopy(s);return;}if(st==='CHAIN_READY'){await renderChainReady(s);return;}if(['ROTATING_PDA','PDA_ROTATED','REBUILDING_SERVER'].includes(st)){pollView(s);return;}if(st==='WALLET_MIGRATION'){placeholderView(s,'wallet');return;}if(st==='MESSAGE_MIGRATION'){placeholderView(s,'messages');return;}if(st==='FINALIZING'){const next=await client.continuePlaceholder();if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}await refresh();return;}if(st==='COMPLETE'){setRoot(note('Смена ключей завершена.'));return;}setRoot(note(`Неизвестное состояние ротации: ${st}`));}catch(e){setRoot(note(`Не удалось прочитать состояние смены ключей: ${e?.message||e}`));}}
|
||||
const resolveBundles=async()=>{
|
||||
if(!cachedOldBundle){if(!oldP?.el.value)throw new Error('Введите старый пароль');cachedOldBundle=await derive(oldP.el.value);}
|
||||
if(!cachedNewBundle){if(!newP?.el.value)throw new Error('Введите новый пароль');cachedNewBundle=await derive(newP.el.value);}
|
||||
if(String(cachedOldBundle?.blockchainPair?.publicKeyB64||'')!==String(s.oldBlockchainKey||''))throw new Error('Старый пароль выводит другой blockchain key');
|
||||
if(String(cachedNewBundle?.blockchainPair?.publicKeyB64||'')!==String(s.newBlockchainKey||''))throw new Error('Новый пароль выводит другой blockchain key');
|
||||
};
|
||||
|
||||
const prepareWallets=async()=>{
|
||||
await resolveBundles();
|
||||
oldWallet=await getSolanaWalletFromStoredSecret(cachedOldBundle.blockchainPair.privatePkcs8B64);
|
||||
newWallet=await getSolanaWalletFromStoredSecret(cachedNewBundle.blockchainPair.privatePkcs8B64);
|
||||
const [oldB,newB]=await Promise.all([
|
||||
getBalanceSol({endpoint:solanaEndpoint,address:oldWallet.address}),
|
||||
getBalanceSol({endpoint:solanaEndpoint,address:newWallet.address}),
|
||||
]);
|
||||
oldBalance=oldB;
|
||||
details.innerHTML=`<strong>Старый кошелёк</strong><span class="meta-muted">${h(oldWallet.address)}</span><span>${h(formatSol(oldB.sol,9))} SOL</span><strong>Новый кошелёк</strong><span class="meta-muted">${h(newWallet.address)}</span><span>${h(formatSol(newB.sol,9))} SOL</span>`;
|
||||
transfer.textContent=Number(oldB.lamports||0)>0?'Перевести SOL и продолжить':'Средств нет — продолжить';
|
||||
transfer.disabled=false;
|
||||
};
|
||||
|
||||
prepare.addEventListener('click',async()=>{prepare.disabled=true;transfer.disabled=true;err.textContent='';try{await prepareWallets();}catch(e){err.textContent=e?.message||String(e);}finally{prepare.disabled=false;}});
|
||||
transfer.addEventListener('click',async()=>{transfer.disabled=true;prepare.disabled=true;skip.disabled=true;err.textContent='';try{
|
||||
if(!oldWallet||!newWallet||!oldBalance)await prepareWallets();
|
||||
if(Number(oldBalance?.lamports||0)>0){
|
||||
const result=await transferAllSol({endpoint:solanaEndpoint,fromKeypair:oldWallet.keypair,toAddress:newWallet.address});
|
||||
details.insertAdjacentHTML('beforeend',`<span class="meta-muted">${result.skipped?'Перевод не потребовался.':`Переведено ${h(formatSol(Number(result.lamports)/1_000_000_000,9))} SOL. Tx: ${h(result.signature)}`}</span>`);
|
||||
}
|
||||
const next=await client.continuePlaceholder();
|
||||
if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}
|
||||
await refresh();
|
||||
}catch(e){err.textContent=e?.message||String(e);transfer.disabled=false;prepare.disabled=false;skip.disabled=false;}});
|
||||
skip.addEventListener('click',async()=>{skip.disabled=true;transfer.disabled=true;prepare.disabled=true;err.textContent='';try{
|
||||
const next=await client.continuePlaceholder();
|
||||
if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}
|
||||
await refresh();
|
||||
}catch(e){err.textContent=e?.message||String(e);skip.disabled=false;transfer.disabled=!oldWallet;prepare.disabled=false;}});
|
||||
|
||||
const nodes=[statusCard(s),info,warning];if(oldP)nodes.push(oldP.wrap);if(newP)nodes.push(newP.wrap);nodes.push(details,prepare,transfer,skip,err);setRoot(...nodes);
|
||||
if(cachedOldBundle&&cachedNewBundle)void prepareWallets().catch(e=>{err.textContent=e?.message||String(e);});
|
||||
}
|
||||
|
||||
function placeholderView(s){const text='Перешифрование старых личных сообщений новым client key пока не реализовано. Этот этап сейчас будет отмечен как NOT_IMPLEMENTED и пропущен.';const go=document.createElement('button');go.className='primary-btn';go.textContent='Продолжить';go.addEventListener('click',async()=>{go.disabled=true;try{const next=await client.continuePlaceholder();if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}await refresh();}catch(e){go.disabled=false;root.append(note(e?.message||String(e)));}});setRoot(statusCard(s),note(text),go);}
|
||||
|
||||
async function refresh(){if(disposed)return;if(timer){clearTimeout(timer);timer=null;}try{const s=await client.status();const st=String(s.rotationStatus||'NONE');if(st==='NONE'){await renderNone();return;}if(st==='COPYING_CHAIN'){if(cachedNewBundle)await runCopy(s);else await askNewPasswordAndCopy(s);return;}if(st==='CHAIN_READY'){await renderChainReady(s);return;}if(['ROTATING_PDA','PDA_ROTATED','REBUILDING_SERVER'].includes(st)){pollView(s);return;}if(st==='WALLET_MIGRATION'){await renderWalletMigration(s);return;}if(st==='MESSAGE_MIGRATION'){placeholderView(s);return;}if(st==='FINALIZING'){const next=await client.continuePlaceholder();if(String(next?.rotationStatus||'')==='COMPLETE'){navigate('my-blockchain-view');return;}await refresh();return;}if(st==='COMPLETE'){setRoot(note('Смена ключей завершена.'));return;}setRoot(note(`Неизвестное состояние ротации: ${st}`));}catch(e){setRoot(note(`Не удалось прочитать состояние смены ключей: ${e?.message||e}`));}}
|
||||
void refresh();screen.cleanup=()=>{disposed=true;if(timer)clearTimeout(timer);};return screen;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user