feat: etiquetas BIP-329 — importar, listar, mostrar en análisis
This commit is contained in:
+73
-3
@@ -1782,7 +1782,7 @@
|
||||
return sections;
|
||||
}
|
||||
|
||||
function PrivacyLab({analysis, tx}) {
|
||||
function PrivacyLab({analysis, tx, labels}) {
|
||||
const [expanded,setExpanded] = useState(null);
|
||||
const [showDidactic,setShowDidactic] = useState(null);
|
||||
const [showReport,setShowReport] = useState(false);
|
||||
@@ -1898,6 +1898,7 @@
|
||||
Wallet posible: <span style={{color:C.amber}}>{wallets.map(w=>w.name||w).join(", ")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -3436,6 +3437,27 @@
|
||||
const [analysis,setAnalysis]=useState(null);
|
||||
const [loading,setLoading]=useState(false);
|
||||
const [err,setErr]=useState(null);
|
||||
const [labels,setLabels]=useState(new Map()); // BIP-329: ref -> label
|
||||
const [showLabelModal,setShowLabelModal]=useState(false);
|
||||
const [labelInfo,setLabelInfo]=useState(null); // {count, file}
|
||||
|
||||
const loadLabels = (file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const map = new Map();
|
||||
const lines = e.target.result.split("\n").filter(l => l.trim());
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
if (obj.ref && obj.label) map.set(obj.ref, obj.label);
|
||||
} catch {}
|
||||
}
|
||||
setLabels(map);
|
||||
setLabelInfo({count: map.size, file: file.name});
|
||||
setTimeout(() => setShowLabelModal(false), 50);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
const analyze=useCallback(async(q)=>{
|
||||
q=(q||query).trim(); if(!q)return;
|
||||
@@ -3460,7 +3482,49 @@
|
||||
return (
|
||||
<div style={{display:"flex",flexDirection:"column",gap:12}}>
|
||||
{!base&&<DemoBanner/>}
|
||||
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center"}}>
|
||||
<SectionTitle accent={C.purple} icon="⌖">AUDITORÍA</SectionTitle>
|
||||
<button onClick={()=>setShowLabelModal(true)}
|
||||
style={{padding:"5px 12px",background:labels.size>0?C.greenMuted:C.bgCard,border:`1px solid ${labels.size>0?C.green:C.border}`,borderRadius:6,color:labels.size>0?C.green:C.t2,fontFamily:"monospace",fontSize:"0.65rem",cursor:"pointer",whiteSpace:"nowrap"}}>
|
||||
{labels.size>0?`✓ ${labels.size} etiquetas`:"Etiquetas ↑"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showLabelModal&&(
|
||||
<Card glow={C.purple}>
|
||||
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:10}}>
|
||||
<div style={{fontSize:"0.65rem",color:C.purple,fontFamily:"monospace",fontWeight:700}}>IMPORTAR ETIQUETAS BIP-329</div>
|
||||
<button onClick={()=>setShowLabelModal(false)} style={{background:"none",border:"none",color:C.t2,cursor:"pointer",fontSize:"1rem",lineHeight:1}}>×</button>
|
||||
</div>
|
||||
<div style={{fontSize:"0.68rem",color:C.t2,marginBottom:12}}>
|
||||
Exporta tus etiquetas desde Sparrow: <span style={{color:C.t1,fontFamily:"monospace"}}>File → Export → Export Labels</span>. El archivo <span style={{color:C.t1,fontFamily:"monospace"}}>.jsonl</span> se carga en memoria — no sale del navegador.
|
||||
</div>
|
||||
{labelInfo&&(
|
||||
<div style={{marginBottom:10}}>
|
||||
<div style={{fontSize:"0.65rem",color:C.green,fontFamily:"monospace",marginBottom:8}}>✓ {labelInfo.file} — {labelInfo.count} etiquetas cargadas</div>
|
||||
<div style={{maxHeight:160,overflowY:"auto",display:"flex",flexDirection:"column",gap:3}}>
|
||||
{[...labels.entries()].map(([ref,lbl])=>(
|
||||
<div key={ref} style={{display:"flex",gap:8,padding:"3px 6px",background:C.bgCard,borderRadius:4,alignItems:"baseline"}}>
|
||||
<span style={{fontSize:"0.58rem",color:C.t2,fontFamily:"monospace",flexShrink:0,maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{ref.length>20?ref.slice(0,10)+"…"+ref.slice(-8):ref}</span>
|
||||
<span style={{fontSize:"0.65rem",color:C.t1,fontFamily:"monospace"}}>{lbl}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<label style={{display:"flex",alignItems:"center",gap:10,padding:"10px 14px",background:C.bgCard,border:`1px dashed ${C.purple}60`,borderRadius:6,cursor:"pointer"}}>
|
||||
<span style={{fontSize:"0.72rem",color:C.purple}}>↑</span>
|
||||
<span style={{fontSize:"0.7rem",color:C.t2}}>Seleccionar archivo .jsonl</span>
|
||||
<input type="file" accept=".jsonl,.json" style={{display:"none"}} onChange={e=>e.target.files[0]&&loadLabels(e.target.files[0])}/>
|
||||
</label>
|
||||
{labels.size>0&&(
|
||||
<button onClick={()=>{setLabels(new Map());setLabelInfo(null);setShowLabelModal(false);}}
|
||||
style={{marginTop:8,padding:"5px 12px",background:"none",border:`1px solid ${C.red}40`,borderRadius:6,color:C.red,fontFamily:"monospace",fontSize:"0.65rem",cursor:"pointer"}}>
|
||||
Borrar etiquetas
|
||||
</button>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<div style={{fontSize:"0.62rem",color:C.t2,fontFamily:"monospace",marginBottom:10}}>ANALIZAR TRANSACCIÓN — introduce un txid</div>
|
||||
@@ -3486,6 +3550,12 @@
|
||||
<div>
|
||||
<div style={{fontSize:"0.6rem",color:C.t2,fontFamily:"monospace",marginBottom:4}}>TRANSACCIÓN ANALIZADA</div>
|
||||
<div style={{fontSize:"0.68rem",fontFamily:"monospace",color:C.blue,wordBreak:"break-all"}}>{tx.txid}</div>
|
||||
{labels&&labels.get(tx.txid)&&(
|
||||
<div style={{marginTop:8,display:"flex",alignItems:"center",gap:8,padding:"6px 10px",background:C.purpleMuted,border:`1px solid ${C.purple}60`,borderRadius:6}}>
|
||||
<span style={{fontSize:"0.7rem"}}>🏷</span>
|
||||
<span style={{fontSize:"0.75rem",color:C.purple,fontFamily:"monospace",fontWeight:600}}>{labels.get(tx.txid)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{tx.status?.confirmed?<Badge color={C.green}>#{fmt.num(tx.status.block_height)}</Badge>:<Badge color={C.amber}>MEMPOOL</Badge>}
|
||||
</div>
|
||||
@@ -3498,8 +3568,8 @@
|
||||
<Tag label="Fecha" value={tx.status?.block_time?fmt.date(tx.status.block_time):"-"}/>
|
||||
</div>
|
||||
</Card>
|
||||
<PrivacyLab analysis={analysis} tx={tx}/>
|
||||
<RastroProcedencia tx={tx} base={base}/>
|
||||
<PrivacyLab analysis={analysis} tx={tx} labels={labels}/>
|
||||
<RastroProcedencia tx={tx} base={base} labels={labels}/>
|
||||
<TxVerifier base={base}/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user