Files
easy-wallet/frontend/src/components/SingleAddress.jsx
Davide Grilli 9ce830eb6d refactor: replace fetch() calls with window.electronAPI in React components
- HDWallet, SingleAddress, DecryptWallet now call window.electronAPI.*
- Remove Vite proxy (no more HTTP backend)
2026-03-09 14:24:04 +01:00

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>
))}
</>
)
}