import { useState } from 'react'
import styles from './FindingCard.module.css'
import VulnerabilityBadge from './VulnerabilityBadge'
function TxRow({ txid }) {
if (!txid) return null
return (
txid
{txid.slice(0, 10)}…{txid.slice(-10)}
)
}
function AddressRow({ item }) {
const { address, role, amount_btc, sats, script_type, ours } = item
const tag = role ?? (script_type ? `${script_type}${ours != null ? (ours ? ' ·ours' : ' ·ext') : ''}` : null)
const amount = amount_btc != null ? `${amount_btc} BTC` : sats != null ? `${sats} sats` : null
return (
{address}
{tag && {tag} }
{amount && {amount} }
)
}
function AddrGroup({ label, items }) {
if (!items?.length) return null
return (
{label}
{items.map((item, i) =>
)}
)
}
function StringList({ label, items }) {
if (!items?.length) return null
return (
{label}
{items.map((s, i) => {s} )}
)
}
function ScalarGroup({ data }) {
const entries = Object.entries(data).filter(([, v]) => typeof v !== 'object')
if (!entries.length) return null
return (
{entries.map(([k, v]) => (
{k.replace(/_/g, ' ')}
{String(v)}
))}
)
}
function DetailsPanel({ details }) {
if (!details || !Object.keys(details).length) return null
const skip = new Set()
const parts = []
if (details.txid) {
parts.push( )
skip.add('txid')
}
if (typeof details.address === 'string') {
parts.push(
{details.address}
)
skip.add('address')
}
const addrFields = [
'our_addresses', 'change_outputs', 'received_outputs',
'dust_inputs', 'normal_inputs', 'tainted_inputs', 'clean_inputs', 'inputs',
]
for (const f of addrFields) {
if (Array.isArray(details[f]) && details[f].length) {
parts.push( )
skip.add(f)
}
}
if (details.funding_sources && typeof details.funding_sources === 'object' && !Array.isArray(details.funding_sources)) {
const rows = Object.entries(details.funding_sources).map(([k, v]) => ({
address: k,
role: Array.isArray(v) ? v.join(', ') : String(v),
}))
parts.push( )
skip.add('funding_sources')
}
for (const f of ['reasons', 'patterns', 'signals', 'script_types']) {
if (Array.isArray(details[f]) && details[f].length) {
parts.push( )
skip.add(f)
}
}
const rest = Object.fromEntries(Object.entries(details).filter(([k]) => !skip.has(k)))
if (Object.keys(rest).length) {
parts.push( )
}
return {parts}
}
export default function FindingCard({ finding }) {
const [open, setOpen] = useState(false)
return (
setOpen(o => !o)}>
{finding.description}
›
{open &&
}
)
}