import { deriveIdentityKey } from '@1sat/utils' import { type DecryptedBackup, decryptBackup, getBackupType, isOneSatBackup, isWifBackup, isYoursWalletBackup, } from 'bitcoin-backup' import { KeyRound, Loader2, Search, Upload, X } from 'lucide-react' import { useCallback, useRef, useState } from 'react' import { deriveAddress } from '../lib/scanner' import type { LegacyKeys } from '../types' import { Button } from './ui/button' import { Card, CardContent, CardHeader, CardTitle } from './ui/card' import { Input } from './ui/input' interface Props { onScan: (keys: LegacyKeys) => void scanning: boolean disabled: boolean } type InputMode = 'choose' | 'backup' | 'wif' function withIdentityKey(keys: LegacyKeys): LegacyKeys { if (keys.identityPk) return keys return { ...keys, identityPk: deriveIdentityKey(keys.payPk, keys.ordPk).toWif(), } } function extractKeys(backup: DecryptedBackup): LegacyKeys | null { if (isOneSatBackup(backup)) { return withIdentityKey({ payPk: backup.payPk, ordPk: backup.ordPk, identityPk: backup.identityPk, }) } if (isYoursWalletBackup(backup)) { return withIdentityKey({ payPk: backup.payPk, ordPk: backup.ordPk, identityPk: backup.identityPk, }) } if (isWifBackup(backup)) { return withIdentityKey({ payPk: backup.wif, ordPk: backup.wif, }) } return null } function tryParseBackup(text: string): LegacyKeys | null { try { const parsed = JSON.parse(text) if (parsed.payPk && parsed.ordPk) { return withIdentityKey({ payPk: parsed.payPk, ordPk: parsed.ordPk, identityPk: parsed.identityPk, }) } if (parsed.accounts && parsed.selectedAccount) { const account = parsed.accounts[parsed.selectedAccount] if (account?.encryptedKeys) { return null } } } catch { // Not JSON } return null } function looksEncrypted(text: string): boolean { if (text.startsWith('{')) return false if (text.startsWith('5') || text.startsWith('K') || text.startsWith('L')) return false return text.length > 50 } export function WifInput({ onScan, scanning, disabled }: Props) { const [mode, setMode] = useState('choose') const [keys, setKeys] = useState(null) const [backupText, setBackupText] = useState('') const [password, setPassword] = useState('') const [needsPassword, setNeedsPassword] = useState(false) const [decrypting, setDecrypting] = useState(false) const [error, setError] = useState('') const [backupType, setBackupType] = useState('') const fileInputRef = useRef(null) const [payWif, setPayWif] = useState('') const [ordWif, setOrdWif] = useState('') const [sameKey, setSameKey] = useState(true) const handleReset = useCallback(() => { setKeys(null) setBackupText('') setPassword('') setNeedsPassword(false) setError('') setBackupType('') setMode('choose') setPayWif('') setOrdWif('') }, []) const processBackupText = useCallback(async (text: string) => { setError('') setBackupText(text) const directKeys = tryParseBackup(text) if (directKeys) { setKeys(directKeys) setBackupType('JSON') return } try { const parsed = JSON.parse(text) if (parsed.encryptedBackup) { setBackupText(parsed.encryptedBackup) setNeedsPassword(true) return } if (parsed.encryptedKeys) { setBackupText(parsed.encryptedKeys) setNeedsPassword(true) return } if (parsed.accounts && parsed.selectedAccount) { const account = parsed.accounts[parsed.selectedAccount] if (account?.encryptedKeys) { setBackupText(account.encryptedKeys) setNeedsPassword(true) return } } } catch { // Not JSON } if (looksEncrypted(text)) { setNeedsPassword(true) return } setError('Unrecognized backup format') }, []) const handleDecrypt = useCallback(async () => { if (!backupText || !password) return setDecrypting(true) setError('') try { const decrypted = await decryptBackup(backupText, password) const extracted = extractKeys(decrypted) if (!extracted) { setError(`Unsupported backup type: ${getBackupType(decrypted)}`) return } setKeys(extracted) setBackupType(getBackupType(decrypted)) setNeedsPassword(false) } catch (e) { setError(e instanceof Error ? e.message : 'Decryption failed') } finally { setDecrypting(false) } }, [backupText, password]) const handleFileUpload = useCallback( (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = () => { processBackupText((reader.result as string).trim()) } reader.readAsText(file) e.target.value = '' }, [processBackupText], ) const handleScan = useCallback(() => { if (keys) { onScan(keys) } else if (mode === 'wif') { const pay = payWif.trim() const ord = sameKey ? pay : ordWif.trim() if (pay) onScan({ payPk: pay, ordPk: ord }) } }, [keys, mode, payWif, ordWif, sameKey, onScan]) if (keys) { return (
Legacy Keys
{backupType && ( {backupType} )}
Pay: {deriveAddress(keys.payPk)}
{keys.ordPk !== keys.payPk && (
Ord: {deriveAddress(keys.ordPk)}
)} {keys.identityPk && keys.identityPk !== keys.payPk && (
ID: {deriveAddress(keys.identityPk)}
)}
) } if (mode === 'choose') { return ( Legacy Keys ) } if (mode === 'backup') { return (
Import Backup
{!needsPassword ? ( <>