feat: etiquetas BIP-329 — importar, listar, mostrar en análisis
This commit is contained in:
+74
-4
@@ -1782,7 +1782,7 @@
|
|||||||
return sections;
|
return sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrivacyLab({analysis, tx}) {
|
function PrivacyLab({analysis, tx, labels}) {
|
||||||
const [expanded,setExpanded] = useState(null);
|
const [expanded,setExpanded] = useState(null);
|
||||||
const [showDidactic,setShowDidactic] = useState(null);
|
const [showDidactic,setShowDidactic] = useState(null);
|
||||||
const [showReport,setShowReport] = useState(false);
|
const [showReport,setShowReport] = useState(false);
|
||||||
@@ -1898,6 +1898,7 @@
|
|||||||
Wallet posible: <span style={{color:C.amber}}>{wallets.map(w=>w.name||w).join(", ")}</span>
|
Wallet posible: <span style={{color:C.amber}}>{wallets.map(w=>w.name||w).join(", ")}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -3436,6 +3437,27 @@
|
|||||||
const [analysis,setAnalysis]=useState(null);
|
const [analysis,setAnalysis]=useState(null);
|
||||||
const [loading,setLoading]=useState(false);
|
const [loading,setLoading]=useState(false);
|
||||||
const [err,setErr]=useState(null);
|
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)=>{
|
const analyze=useCallback(async(q)=>{
|
||||||
q=(q||query).trim(); if(!q)return;
|
q=(q||query).trim(); if(!q)return;
|
||||||
@@ -3460,7 +3482,49 @@
|
|||||||
return (
|
return (
|
||||||
<div style={{display:"flex",flexDirection:"column",gap:12}}>
|
<div style={{display:"flex",flexDirection:"column",gap:12}}>
|
||||||
{!base&&<DemoBanner/>}
|
{!base&&<DemoBanner/>}
|
||||||
<SectionTitle accent={C.purple} icon="⌖">AUDITORÍA</SectionTitle>
|
<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>
|
<Card>
|
||||||
<div style={{fontSize:"0.62rem",color:C.t2,fontFamily:"monospace",marginBottom:10}}>ANALIZAR TRANSACCIÓN — introduce un txid</div>
|
<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>
|
||||||
<div style={{fontSize:"0.6rem",color:C.t2,fontFamily:"monospace",marginBottom:4}}>TRANSACCIÓN ANALIZADA</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>
|
<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>
|
</div>
|
||||||
{tx.status?.confirmed?<Badge color={C.green}>#{fmt.num(tx.status.block_height)}</Badge>:<Badge color={C.amber}>MEMPOOL</Badge>}
|
{tx.status?.confirmed?<Badge color={C.green}>#{fmt.num(tx.status.block_height)}</Badge>:<Badge color={C.amber}>MEMPOOL</Badge>}
|
||||||
</div>
|
</div>
|
||||||
@@ -3498,8 +3568,8 @@
|
|||||||
<Tag label="Fecha" value={tx.status?.block_time?fmt.date(tx.status.block_time):"-"}/>
|
<Tag label="Fecha" value={tx.status?.block_time?fmt.date(tx.status.block_time):"-"}/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<PrivacyLab analysis={analysis} tx={tx}/>
|
<PrivacyLab analysis={analysis} tx={tx} labels={labels}/>
|
||||||
<RastroProcedencia tx={tx} base={base}/>
|
<RastroProcedencia tx={tx} base={base} labels={labels}/>
|
||||||
<TxVerifier base={base}/>
|
<TxVerifier base={base}/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user