import { useState, useCallback } from 'react';
import {
generateSeed,
createWallet,
deriveAddresses,
saveWallet,
loadWallet,
listWallets,
type AddressInfo,
type WalletMeta,
} from '../api';
function shortenAddr(addr: string): string {
if (addr.length < 20) return addr;
return addr.slice(0, 10) + '…' + addr.slice(-6);
}
interface WalletCreationProps {
onComplete: (wallet: any, addresses: AddressInfo[], seed: string) => void;
}
export default function WalletCreation({ onComplete }: WalletCreationProps) {
const [step, setStep] = useState(0);
const [seed, setSeed] = useState('');
const [walletName, setWalletName] = useState('Wallet 1');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [walletData, setWalletData] = useState<any>(null);
const [addresses, setAddresses] = useState<AddressInfo[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [showLoad, setShowLoad] = useState(false);
const [savedWallets, setSavedWallets] = useState<WalletMeta[]>([]);
const [savedPath, setSavedPath] = useState('');
const [seedConfirmed, setSeedConfirmed] = useState(false);
const loadWalletList = useCallback(async () => {
try {
const result = await listWallets();
const filtered = (result.wallets || []).filter((w: WalletMeta) =>
!w.name?.startsWith('multisig_Test')
);
setSavedWallets(filtered);
setShowLoad(true);
} catch { /* ignore */ }
}, []);
const handleGenerate = useCallback(async () => {
setLoading(true);
setError('');
try {
const mnemonic = await generateSeed();
setSeed(mnemonic);
setStep(1);
} catch (e: any) {
setError(e.message || 'Failed to generate seed');
} finally {
setLoading(false);
}
}, []);
const handleCreate = useCallback(async () => {
setLoading(true);
setError('');
try {
if (password && password !== confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
const result = await createWallet(seed, walletName, password);
try {
const saveResult = await saveWallet(result.wallet);
result.wallet.file_path = saveResult.path;
setSavedPath(saveResult.path);
} catch (saveErr: any) {
setError((saveErr.message || 'Failed to save wallet') + ' — wallet created but not saved');
}
setWalletData(result.wallet);
setAddresses(result.addresses);
setStep(2);
} catch (e: any) {
setError(e.message || 'Failed to create wallet');
} finally {
setLoading(false);
}
}, [seed, walletName, password, confirmPassword]);
const handleLoad = useCallback(async (path: string, needPassword: boolean) => {
setLoading(true);
setError('');
try {
const result = await loadWallet(path, needPassword ? password : '');
const addrs = await deriveAddresses(result.wallet, 5);
setWalletData(result.wallet);
setAddresses(addrs);
setStep(2);
setShowLoad(false);
setPassword('');
} catch (e: any) {
setError(e.message || 'Failed to load wallet');
} finally {
setLoading(false);
}
}, [password]);
const handleFinish = useCallback(() => {
if (walletData) {
onComplete(walletData, addresses, seed);
}
}, [walletData, addresses, seed, onComplete]);
// Step 2 — Wallet created, show seed + addresses
if (step === 2 && walletData) {
return (
<div className="wallet-creation-page">
<div className="wallet-creation-card" style={{ maxWidth: '600px' }}>
<h2>Wallet Created ✓</h2>
<p className="subtitle">{walletData.name} — {walletData.fingerprint}</p>
<div className="section">
<div className="section-header">
<h3>⚠️ Seed Phrase — Write this down!</h3>
</div>
{seed ? (
<>
<div className="seed-display">{seed}</div>
{!seedConfirmed && (
<button
className="btn btn-secondary"
style={{ width: '100%' }}
onClick={() => setSeedConfirmed(true)}
>
✓ I have written down my seed phrase
</button>
)}
{seedConfirmed && (
<div className="success-box" style={{ textAlign: 'center' }}>
✓ Seed phrase cleared from memory
</div>
)}
</>
) : (
<p className="text-secondary" style={{ fontSize: '14px' }}>
This wallet was loaded from disk — no seed to display.
</p>
)}
</div>
<div className="section">
<div className="section-header">
<h3>First 5 Addresses</h3>
</div>
{addresses.map(a => (
<div key={a.index} className="info-row" style={{ marginBottom: '4px' }}>
<span className="mono" style={{ fontSize: '12px' }}>
{a.index} — {shortenAddr(a.address)}
</span>
</div>
))}
</div>
{savedPath && (
<div className="success-box" style={{ marginBottom: '20px' }}>
✓ Saved to <span className="mono">{savedPath}</span>
</div>
)}
<button className="btn btn-primary" style={{ width: '100%' }} onClick={handleFinish}>
Open Wallet →
</button>
</div>
</div>
);
}
// Steps 0-1 — Creation form
return (
<div className="wallet-creation-page">
<div className="wallet-creation-card">
<h2>Bitcoin Wallet</h2>
<p className="subtitle">Create a new BIP84 Native SegWit wallet</p>
{error && <div className="error-box">{error}</div>}
{step === 0 && (
<div className="creation-step">
<div style={{ marginBottom: '20px' }}>
<p style={{ fontSize: '14px', color: 'var(--text-secondary)', marginBottom: '16px' }}>
Generate a random 12-word seed phrase (BIP39)
</p>
<button
className="btn btn-primary"
style={{ width: '100%' }}
onClick={handleGenerate}
disabled={loading}
>
{loading ? 'Generating…' : 'Generate Seed'}
</button>
</div>
<div style={{ textAlign: 'center', color: 'var(--text-tertiary)', margin: '16px 0', fontSize: '13px' }}>
or
</div>
{!showLoad ? (
<button
className="btn btn-secondary"
style={{ width: '100%' }}
onClick={() => { setShowLoad(true); loadWalletList(); }}
>
📂 Load Existing Wallet
</button>
) : (
<div>
{savedWallets.filter(w => w.encrypted).length > 0 && (
<div className="input-group" style={{ marginBottom: '12px' }}>
<label className="input-label">Password (for encrypted wallets)</label>
<input
type="password"
className="input"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Enter wallet password"
/>
</div>
)}
{savedWallets.length === 0 ? (
<p className="text-tertiary" style={{ fontSize: '13px' }}>No saved wallets found</p>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '12px' }}>
{savedWallets.map(w => (
<button
key={w.path}
className="sidebar-wallet-item"
onClick={() => handleLoad(w.path, w.encrypted)}
>
<span className={`sidebar-wallet-dot ${w.encrypted ? 'locked' : ''}`} />
<span className="sidebar-wallet-name">{w.name}</span>
<span className="sidebar-wallet-fp">{w.fingerprint}</span>
</button>
))}
</div>
)}
<button className="btn btn-ghost" style={{ width: '100%' }} onClick={() => setShowLoad(false)}>
← Back
</button>
</div>
)}
</div>
)}
{step === 1 && (
<div className="creation-step">
<div style={{ marginBottom: '20px' }}>
<div className="section-header">
<h3>⚠️ Write down your seed phrase</h3>
</div>
<div className="seed-display">{seed}</div>
</div>
<div className="input-group">
<label className="input-label">Wallet name</label>
<input
className="input"
value={walletName}
onChange={e => setWalletName(e.target.value)}
placeholder="My Wallet"
/>
</div>
<div className="input-group" style={{ marginTop: '12px' }}>
<label className="input-label">Password (optional)</label>
<input
type="password"
className="input"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Encrypt seed with Argon2id"
/>
{password && (
<input
type="password"
className="input"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
placeholder="Confirm password"
style={{ marginTop: '6px' }}
/>
)}
</div>
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
<button className="btn btn-secondary" onClick={() => setStep(0)} style={{ flex: 1 }}>
Back
</button>
<button
className="btn btn-primary"
onClick={handleCreate}
disabled={loading || !walletName.trim()}
style={{ flex: 2 }}
>
{loading ? 'Creating…' : 'Create Wallet'}
</button>
</div>
</div>
)}
</div>
</div>
);
}