feat: exportar UTXO Map a CSV (local, sin salir del nodo)
This commit is contained in:
@@ -12,6 +12,20 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/):
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.1.0] — 2026-06-01
|
||||||
|
|
||||||
|
### Añadido
|
||||||
|
|
||||||
|
- Exportación del UTXO Map a CSV. Un botón "↓ CSV" en el UTXO Map descarga
|
||||||
|
todos los UTXOs de la dirección analizada con su información: txid, vout,
|
||||||
|
valor en sats y en BTC, estado de confirmación, altura de bloque, score
|
||||||
|
de privacidad y problemas detectados. El archivo se genera en el navegador
|
||||||
|
y se descarga en local — no pasa por ningún servidor, no sale nada del nodo.
|
||||||
|
Pensado para abrir en una hoja de cálculo y ordenar o filtrar los UTXOs
|
||||||
|
por privacidad.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.0.0] — 2026-05-31
|
## [1.0.0] — 2026-05-31
|
||||||
|
|
||||||
Primera versión completa y funcional. Suite de auditoría de privacidad
|
Primera versión completa y funcional. Suite de auditoría de privacidad
|
||||||
|
|||||||
@@ -2141,11 +2141,58 @@
|
|||||||
const avgPrivacy = privacyScores.length>0
|
const avgPrivacy = privacyScores.length>0
|
||||||
? Math.round(privacyScores.reduce((s,p)=>s+p.score,0)/privacyScores.length) : null;
|
? Math.round(privacyScores.reduce((s,p)=>s+p.score,0)/privacyScores.length) : null;
|
||||||
|
|
||||||
|
// ── Exportar UTXOs a CSV ──────────────────────────────────────────
|
||||||
|
// Genera el archivo en el navegador y lo descarga en local.
|
||||||
|
// No pasa por ningún servidor, no sale nada del nodo.
|
||||||
|
const exportUtxosCSV = () => {
|
||||||
|
if(!utxos || utxos.length===0) return;
|
||||||
|
// Escapar campos: si llevan coma, comilla o salto, van entre comillas
|
||||||
|
const esc = (val) => {
|
||||||
|
const s = String(val ?? "");
|
||||||
|
return /[",\n]/.test(s) ? '"' + s.replace(/"/g,'""') + '"' : s;
|
||||||
|
};
|
||||||
|
const cabeceras = ["txid","vout","valor_sats","valor_btc","confirmado","altura_bloque","privacidad_score","problemas"];
|
||||||
|
const filas = utxos.map((u,i) => {
|
||||||
|
const priv = privacyScores[i] || {score:"", issues:[]};
|
||||||
|
return [
|
||||||
|
u.txid,
|
||||||
|
u.vout,
|
||||||
|
u.value,
|
||||||
|
(u.value/1e8).toFixed(8),
|
||||||
|
u.status?.confirmed ? "si" : "no",
|
||||||
|
u.status?.confirmed ? (u.status.block_height ?? "") : "mempool",
|
||||||
|
priv.score,
|
||||||
|
(priv.issues || []).join("; "),
|
||||||
|
].map(esc).join(",");
|
||||||
|
});
|
||||||
|
const csv = [cabeceras.join(","), ...filas].join("\n");
|
||||||
|
// BOM para que Excel abra los acentos bien + descarga vía Blob
|
||||||
|
const blob = new Blob(["\uFEFF" + csv], {type:"text/csv;charset=utf-8"});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
const fecha = new Date().toISOString().slice(0,10);
|
||||||
|
const addrCorto = (addr || "utxos").slice(0,12);
|
||||||
|
a.href = url;
|
||||||
|
a.download = `txoko-utxos-${addrCorto}-${fecha}.csv`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<Card>
|
<Card>
|
||||||
<div style={{fontSize:"0.62rem",color:C.purple,fontFamily:"monospace",textTransform:"uppercase",letterSpacing:"0.2em",marginBottom:8,display:"flex",alignItems:"center",gap:8}}>
|
<div style={{fontSize:"0.62rem",color:C.purple,fontFamily:"monospace",textTransform:"uppercase",letterSpacing:"0.2em",marginBottom:8,display:"flex",alignItems:"center",gap:8}}>
|
||||||
<span>◎</span> UTXO Map
|
<span>◎</span> UTXO Map
|
||||||
<div style={{flex:1,height:1,background:`linear-gradient(to right,${C.purple}40,transparent)`}}/>
|
<div style={{flex:1,height:1,background:`linear-gradient(to right,${C.purple}40,transparent)`}}/>
|
||||||
|
{utxos&&utxos.length>0&&(
|
||||||
|
<button onClick={exportUtxosCSV} title="Descargar los UTXOs en CSV (local, no sale del nodo)"
|
||||||
|
style={{padding:"4px 10px",background:C.bg,border:`1px solid ${C.purple}40`,borderRadius:5,color:C.purple,fontFamily:"monospace",fontSize:"0.6rem",letterSpacing:"0.08em",cursor:"pointer",whiteSpace:"nowrap"}}
|
||||||
|
onMouseEnter={e=>e.currentTarget.style.borderColor=C.purple}
|
||||||
|
onMouseLeave={e=>e.currentTarget.style.borderColor=C.purple+"40"}>
|
||||||
|
↓ CSV
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{fontSize:"0.65rem",color:C.t2,marginBottom:12,lineHeight:1.5}}>
|
<div style={{fontSize:"0.65rem",color:C.t2,marginBottom:12,lineHeight:1.5}}>
|
||||||
Analiza los UTXOs de una dirección con score de privacidad por UTXO y contexto de origen. Máx. {MAX_UTXOS} UTXOs.
|
Analiza los UTXOs de una dirección con score de privacidad por UTXO y contexto de origen. Máx. {MAX_UTXOS} UTXOs.
|
||||||
|
|||||||
Reference in New Issue
Block a user