- HDWallet, SingleAddress, DecryptWallet now call window.electronAPI.* - Remove Vite proxy (no more HTTP backend)
173 lines
6.4 KiB
JavaScript
173 lines
6.4 KiB
JavaScript
import { useState } from 'react'
|
|
import CopyButton from './CopyButton'
|
|
|
|
const TABS = [
|
|
{ id: 'p2pkh', label: 'P2PKH', desc: 'Legacy · starts with 1 / m' },
|
|
{ id: 'p2wpkh', label: 'P2WPKH', desc: 'Native SegWit · bc1q / tb1q' },
|
|
{ id: 'p2tr', label: 'P2TR', desc: 'Taproot · bc1p / tb1p' },
|
|
{ id: 'p2pk', label: 'P2PK', desc: 'Pay-to-PubKey (no address)' },
|
|
{ id: 'p2sh', label: 'P2SH', desc: 'Multisig · starts with 3 / 2' },
|
|
]
|
|
|
|
export default function SingleAddress({ initialTab = 'p2pkh' }) {
|
|
const [tab, setTab] = useState(initialTab)
|
|
const [network, setNetwork] = useState('mainnet')
|
|
const [compressed, setCompressed] = useState(true)
|
|
const [m, setM] = useState(2)
|
|
const [n, setN] = useState(3)
|
|
const [result, setResult] = useState(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
|
|
const generate = async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
setResult(null)
|
|
try {
|
|
let args = { network }
|
|
if (tab === 'p2sh') args = { ...args, m, n, compressed }
|
|
else if (tab !== 'p2tr') args = { ...args, compressed }
|
|
|
|
const fn = window.electronAPI[tab === 'p2pkh' ? 'p2pkh' : tab === 'p2wpkh' ? 'p2wpkh' : tab === 'p2tr' ? 'p2tr' : tab === 'p2pk' ? 'p2pk' : 'p2sh']
|
|
setResult(await fn(args))
|
|
} catch (e) {
|
|
setError(e.message)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const currentTab = TABS.find(t => t.id === tab)
|
|
|
|
return (
|
|
<div>
|
|
<div className="page-title">Single Addresses</div>
|
|
<div className="page-subtitle">Generate individual Bitcoin addresses and key pairs</div>
|
|
|
|
<div className="tabs">
|
|
{TABS.map(t => (
|
|
<button key={t.id} className={`tab${tab === t.id ? ' active' : ''}`} onClick={() => { setTab(t.id); setResult(null); setError(null) }}>
|
|
{t.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* ── Form ── */}
|
|
<div className="card">
|
|
<div className="card-title">{currentTab.label} <span style={{ color: 'var(--text-xs)', fontWeight: 400 }}>— {currentTab.desc}</span></div>
|
|
|
|
<div className="form-grid" style={{ marginBottom: 14 }}>
|
|
<div className="form-group">
|
|
<label className="form-label">Network</label>
|
|
<select className="form-select" value={network} onChange={e => setNetwork(e.target.value)}>
|
|
<option value="mainnet">Mainnet</option>
|
|
<option value="testnet">Testnet</option>
|
|
<option value="regtest">Regtest</option>
|
|
</select>
|
|
</div>
|
|
|
|
{tab === 'p2sh' && (
|
|
<>
|
|
<div className="form-group">
|
|
<label className="form-label">Required signatures (m)</label>
|
|
<input className="form-input" type="number" min="1" max="16" value={m} onChange={e => setM(parseInt(e.target.value))} />
|
|
</div>
|
|
<div className="form-group">
|
|
<label className="form-label">Total keys (n)</label>
|
|
<input className="form-input" type="number" min="1" max="16" value={n} onChange={e => setN(parseInt(e.target.value))} />
|
|
<span className="form-hint">{m}-of-{n} multisig</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{tab !== 'p2tr' && (
|
|
<div className="checkbox-group" style={{ marginBottom: 14 }}>
|
|
<input type="checkbox" id="compressed" checked={compressed} onChange={e => setCompressed(e.target.checked)} />
|
|
<label htmlFor="compressed">Compressed public key (33 bytes)</label>
|
|
</div>
|
|
)}
|
|
|
|
<button className="btn btn-primary" onClick={generate} disabled={loading}>
|
|
{loading ? <><span className="spinner" /> Generating…</> : '⚡ Generate'}
|
|
</button>
|
|
</div>
|
|
|
|
{error && <div className="alert alert-error">{error}</div>}
|
|
|
|
{result && <ResultCard result={result} tab={tab} />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ResultCard({ result, tab }) {
|
|
const fields = buildFields(result, tab)
|
|
return (
|
|
<div className="card">
|
|
<div className="card-title">Result</div>
|
|
{tab === 'p2sh' ? (
|
|
<P2SHResult result={result} />
|
|
) : (
|
|
fields.map(([label, value]) => (
|
|
<div className="kv-row" key={label}>
|
|
<span className="kv-label">{label}</span>
|
|
<span className="kv-value">{value}</span>
|
|
<span className="kv-actions"><CopyButton text={value} /></span>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function buildFields(result, tab) {
|
|
const rows = [
|
|
['Network', result.network],
|
|
['Script type', result.script_type],
|
|
]
|
|
if (result.address) rows.push(['Address', result.address])
|
|
if (result.public_key_hex) rows.push(['Public key (hex)', result.public_key_hex])
|
|
if (result.internal_pubkey_x_hex) rows.push(['Internal pubkey (x)', result.internal_pubkey_x_hex])
|
|
if (result.private_key_wif) rows.push(['Private key (WIF)', result.private_key_wif])
|
|
if (result.private_key_hex) rows.push(['Private key (hex)', result.private_key_hex])
|
|
return rows
|
|
}
|
|
|
|
function P2SHResult({ result }) {
|
|
return (
|
|
<>
|
|
{[
|
|
['Network', result.network],
|
|
['Script type', result.script_type],
|
|
['Configuration', `${result.m}-of-${result.n}`],
|
|
['Address', result.address],
|
|
['Redeem script', result.redeem_script_hex],
|
|
].map(([label, value]) => (
|
|
<div className="kv-row" key={label}>
|
|
<span className="kv-label">{label}</span>
|
|
<span className="kv-value">{value}</span>
|
|
<span className="kv-actions"><CopyButton text={value} /></span>
|
|
</div>
|
|
))}
|
|
<hr className="divider" />
|
|
<div className="card-title" style={{ marginTop: 4 }}>Participants</div>
|
|
{result.participants.map((p, i) => (
|
|
<div className="participant-card" key={i}>
|
|
<div className="participant-title">Key {i + 1}</div>
|
|
{[
|
|
['Public key', p.public_key_hex],
|
|
['Private key (WIF)', p.private_key_wif],
|
|
['Private key (hex)', p.private_key_hex],
|
|
].map(([label, value]) => (
|
|
<div className="kv-row" key={label} style={{ paddingTop: 4, paddingBottom: 4 }}>
|
|
<span className="kv-label">{label}</span>
|
|
<span className="kv-value">{value}</span>
|
|
<span className="kv-actions"><CopyButton text={value} /></span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</>
|
|
)
|
|
}
|