import { deriveEd25519FromPassword } from './crypto-utils.js'; import { extractDeviceKey32FromStoredValue } from './device-key-utils.js'; import { loadEncryptedUserSecrets } from './key-vault.js'; const DEFAULT_SOLANA_ENDPOINT = 'https://api.devnet.solana.com'; const TOPUP_SITE_URL = 'https://shine-promo-solana-devnet.shineup.me/'; let solanaLibPromise = null; function normalizeEndpoint(url) { const raw = String(url || '').trim(); if (!raw) return DEFAULT_SOLANA_ENDPOINT; return raw; } async function loadSolanaLib() { if (!solanaLibPromise) { solanaLibPromise = import('https://esm.sh/@solana/web3.js@1.98.4?bundle'); } return solanaLibPromise; } async function keypairFromPkcs8(pkcs8B64) { const solana = await loadSolanaLib(); const seed32 = extractDeviceKey32FromStoredValue(pkcs8B64); return solana.Keypair.fromSeed(seed32); } export async function deriveWalletFromPassword(password) { const keyBundle = await deriveEd25519FromPassword(String(password ?? ''), 'dev.key'); const keypair = await keypairFromPkcs8(keyBundle.privatePkcs8B64); return { address: keypair.publicKey.toBase58(), keypair, devicePublicKeyB64: keyBundle.publicKeyB64, devicePrivatePkcs8B64: keyBundle.privatePkcs8B64, }; } export async function getWalletFromStoredDeviceKey({ login, storagePwd }) { const cleanLogin = String(login || '').trim(); const cleanPwd = String(storagePwd || '').trim(); if (!cleanLogin || !cleanPwd) { throw new Error('Нет активной сессии для доступа к wallet.key'); } const secrets = await loadEncryptedUserSecrets(cleanLogin, cleanPwd); const devicePrivate = String(secrets?.deviceKey || '').trim(); if (!devicePrivate) { throw new Error('На устройстве не найден device.key (wallet.key)'); } const keypair = await keypairFromPkcs8(devicePrivate); return { address: keypair.publicKey.toBase58(), keypair, devicePrivatePkcs8B64: devicePrivate, }; } export async function getBalanceSol({ endpoint, address }) { const solana = await loadSolanaLib(); const rpc = normalizeEndpoint(endpoint); const conn = new solana.Connection(rpc, 'confirmed'); const pubkey = new solana.PublicKey(String(address || '').trim()); const lamports = await conn.getBalance(pubkey, 'confirmed'); return { endpoint: rpc, lamports, sol: lamports / solana.LAMPORTS_PER_SOL, }; } export async function requestAirdropSol({ endpoint, address, amountSol = 1 }) { const solana = await loadSolanaLib(); const rpc = normalizeEndpoint(endpoint); const conn = new solana.Connection(rpc, 'confirmed'); const pubkey = new solana.PublicKey(String(address || '').trim()); const lamports = Math.max(1, Math.floor(Number(amountSol) * solana.LAMPORTS_PER_SOL)); const signature = await conn.requestAirdrop(pubkey, lamports); await conn.confirmTransaction(signature, 'confirmed'); return { endpoint: rpc, signature, lamports }; } export async function transferSol({ endpoint, fromKeypair, toAddress, amountSol }) { const solana = await loadSolanaLib(); const rpc = normalizeEndpoint(endpoint); const cleanTo = String(toAddress || '').trim(); const amount = Number(amountSol); if (!cleanTo) throw new Error('Не указан адрес получателя'); if (!Number.isFinite(amount) || amount <= 0) throw new Error('Сумма перевода должна быть больше 0'); const conn = new solana.Connection(rpc, 'confirmed'); const lamports = Math.floor(amount * solana.LAMPORTS_PER_SOL); if (lamports <= 0) throw new Error('Сумма слишком мала'); const tx = new solana.Transaction().add( solana.SystemProgram.transfer({ fromPubkey: fromKeypair.publicKey, toPubkey: new solana.PublicKey(cleanTo), lamports, }), ); const signature = await solana.sendAndConfirmTransaction(conn, tx, [fromKeypair], { commitment: 'confirmed', }); return { endpoint: rpc, signature, lamports }; } export function formatSol(value, digits = 6) { const n = Number(value); if (!Number.isFinite(n)) return '0'; return n.toLocaleString('ru-RU', { minimumFractionDigits: 0, maximumFractionDigits: digits, }); } export function getTopupSiteUrl(walletAddress = '') { const cleanWallet = String(walletAddress || '').trim(); if (!cleanWallet) return TOPUP_SITE_URL; try { const url = new URL(TOPUP_SITE_URL); url.searchParams.set('wallet', cleanWallet); return url.toString(); } catch { return `${TOPUP_SITE_URL}?wallet=${encodeURIComponent(cleanWallet)}`; } }