Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4deb711d5 | ||
|
|
af49ea630b | ||
|
|
98621f956e | ||
|
|
d3caaf6186 | ||
|
|
96517894c5 | ||
|
|
0acfb35bf8 | ||
|
|
bb9b92d9b9 | ||
|
|
fd9127880b | ||
|
|
0223bb16ef | ||
|
|
a696e55bef | ||
|
|
8a5efad2ed | ||
|
|
6f721a535c | ||
|
|
020f9318aa | ||
|
|
fd2e38fd45 | ||
|
|
924b9069aa | ||
|
|
e0bc42c34e | ||
|
|
eac6208405 | ||
|
|
fdd80b689a | ||
|
|
f15d82a352 | ||
|
|
826b9cb8e2 | ||
|
|
29e8e184e8 | ||
|
|
2f5cc9a0ec | ||
|
|
5f4f891d1f | ||
|
|
27f23cc967 | ||
|
|
b37b9a125a | ||
|
|
c86134cc24 | ||
|
|
ebc1a8017f | ||
|
|
0ecd54eb1d | ||
|
|
84e16cb3fd | ||
|
|
d6dc8f6a90 | ||
|
|
14a33ed29a | ||
|
|
f5cffa3f8a | ||
|
|
de3ab5cc7e | ||
|
|
55297ecae6 | ||
|
|
2c46ceda31 | ||
|
|
1c25622b0e | ||
|
|
031c55714f | ||
|
|
35c74ea8f2 | ||
|
|
1ddacdf86e | ||
|
|
8ae135e739 | ||
|
|
4354e661c2 | ||
|
|
68ef8f2179 | ||
|
|
26a8650583 | ||
|
|
2b68a7b7c7 | ||
|
|
22ea8b5add | ||
|
|
aa7f23de33 | ||
|
|
0fc9cb8980 | ||
|
|
625fd9e989 | ||
|
|
d0b10d4a99 | ||
|
|
01fe01d39d | ||
|
|
57f9576860 | ||
|
|
dd78d35bdb | ||
|
|
19211af4c8 | ||
|
|
55a8cf456b | ||
|
|
a260e376bb | ||
|
|
37ae3aef70 | ||
|
|
ee67c2164e | ||
|
|
11b74c92f5 | ||
|
|
5aedec0cb3 | ||
|
|
fe2aa0597c | ||
|
|
6626cd1325 | ||
|
|
cb4535c35d | ||
|
|
e71bd08f76 | ||
|
|
8f902ba06e | ||
|
|
e64292531e |
@@ -29,3 +29,4 @@ MEJORAS.md
|
|||||||
COHERENCIA.md
|
COHERENCIA.md
|
||||||
|
|
||||||
GIT.md
|
GIT.md
|
||||||
|
REPASO.md
|
||||||
|
|||||||
@@ -10,6 +10,37 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/):
|
|||||||
- **MENOR** — características nuevas que no rompen lo anterior
|
- **MENOR** — características nuevas que no rompen lo anterior
|
||||||
- **PARCHE** — arreglos de errores
|
- **PARCHE** — arreglos de errores
|
||||||
|
|
||||||
|
---
|
||||||
|
## [1.13.0] — 2026-08-08
|
||||||
|
### Añadido
|
||||||
|
- **Nueva comprobación: direcciones que quedan vinculadas entre sí.** Era el
|
||||||
|
hueco de fondo del analizador: medía la privacidad desde el lado de quien
|
||||||
|
*construye* la transacción (¿se ve mi cambio?, ¿pago cifras redondas?) y no
|
||||||
|
desde el lado de los dueños de las monedas gastadas, que es donde el daño es
|
||||||
|
irreversible. Gastar N direcciones distintas a la vez las enlaza para siempre
|
||||||
|
por CIOH, y eso no se decía en ningún sitio. El peso sube con el número de
|
||||||
|
direcciones y no aplica en CoinJoin, donde romper esa vinculación es el
|
||||||
|
objetivo.
|
||||||
|
|
||||||
|
### Corregido
|
||||||
|
- **El explorador mostraba saldo cero en direcciones con mucho historial.** El
|
||||||
|
backend de Mempool sobre Fulcrum devuelve los importes a cero cuando la
|
||||||
|
dirección tiene muchas transacciones, aunque el contador venga bien — y la app
|
||||||
|
los presentaba como ciertos. Una dirección con 492 transacciones aparecía con
|
||||||
|
"Balance: 0,00000000 BTC". Ahora se detecta la inconsistencia (tener
|
||||||
|
transacciones y no haber recibido nada es imposible en la cadena), los
|
||||||
|
importes se muestran como "no disponible" y se explica por qué. El contador de
|
||||||
|
transacciones y el historial, que sí son correctos, se mantienen.
|
||||||
|
- Mismo arreglo en el perfil de dirección del peritaje: antes concluía "no es
|
||||||
|
hot wallet" cuando en realidad no había podido evaluar la señal de flujo.
|
||||||
|
- **El fingerprinting atribuía transacciones a Electrum por AUSENCIA de rasgos**
|
||||||
|
(locktime 0, sin RBF, sequence por defecto). Es justo al revés: Electrum
|
||||||
|
moderno pone locktime a la altura actual y señaliza RBF. Lo que esas señales
|
||||||
|
describen no es un monedero concreto, sino software que no configura nada.
|
||||||
|
Ahora Electrum exige señales positivas y aparece un resultado nuevo, **"sin
|
||||||
|
rasgos distintivos"**, que además no penaliza: no dejar huella es lo contrario
|
||||||
|
de tener una huella identificable.
|
||||||
|
|
||||||
---
|
---
|
||||||
## [1.12.1] — 2026-07-27
|
## [1.12.1] — 2026-07-27
|
||||||
### Corregido
|
### Corregido
|
||||||
|
|||||||
+110
-13
@@ -967,18 +967,47 @@
|
|||||||
// ─── Electrum ──────────────────────────────────────────────────
|
// ─── Electrum ──────────────────────────────────────────────────
|
||||||
// Guardia: Electrum usa P2WPKH por defecto. Si no hay inputs P2WPKH,
|
// Guardia: Electrum usa P2WPKH por defecto. Si no hay inputs P2WPKH,
|
||||||
// no tiene sentido puntuar Electrum (evita falsos positivos en P2TR/P2PKH).
|
// no tiene sentido puntuar Electrum (evita falsos positivos en P2TR/P2PKH).
|
||||||
|
// Antes se puntuaba Electrum por AUSENCIA de rasgos: locktime=0, sin
|
||||||
|
// RBF, sequence por defecto. Eso es exactamente al revés — Electrum
|
||||||
|
// moderno (4.x, desde 2020) pone locktime a la altura actual para
|
||||||
|
// protegerse del fee sniping y señaliza RBF por defecto. Lo que aquellas
|
||||||
|
// señales describen no es un wallet concreto, sino software que no
|
||||||
|
// configura nada: un script hecho a medida.
|
||||||
|
//
|
||||||
|
// El fallo salió analizando la transacción de un robo real: la app la
|
||||||
|
// atribuía a "Electrum (PROBABLE)" cuando sus rasgos eran justo los
|
||||||
|
// contrarios a los de Electrum. Un silencio leído como una afirmación.
|
||||||
if (allP2wpkh) {
|
if (allP2wpkh) {
|
||||||
let score = 2, sigs = ["inputs P2WPKH uniformes"]; // incluido por la guardia
|
let score = 0, sigs = [];
|
||||||
if (locktime === 0) { score+=1; sigs.push("locktime=0"); }
|
if (locktimeIsBlock) { score+=3; sigs.push("locktime=altura actual (anti-fee-sniping)"); }
|
||||||
if (!hasRbfOptin) { score+=1; sigs.push("sin RBF"); }
|
if (hasRbfOptin) { score+=2; sigs.push("RBF señalizado"); }
|
||||||
if (allInputs.every(v=>v.sequence===0xffffffff)) { score+=2; sigs.push("sequence=0xFFFFFFFF"); }
|
|
||||||
const allOutSameTypeAsIn = allOutputs.length > 0 &&
|
const allOutSameTypeAsIn = allOutputs.length > 0 &&
|
||||||
allOutputs.every(o => o.scriptpubkey_type === inTypes[0]);
|
allOutputs.every(o => o.scriptpubkey_type === inTypes[0]);
|
||||||
if (allOutSameTypeAsIn) { score+=1; sigs.push("outputs del mismo tipo que inputs"); }
|
if (allOutSameTypeAsIn) { score+=1; sigs.push("outputs del mismo tipo que inputs"); }
|
||||||
if (allOutputs.length === 2) { score+=1; sigs.push("2 outputs"); }
|
if (allOutputs.length === 2) { score+=1; sigs.push("2 outputs"); }
|
||||||
|
if (score >= 2) sigs.unshift("inputs P2WPKH uniformes");
|
||||||
|
// Hace falta al menos una señal POSITIVA fuerte, no solo ausencias.
|
||||||
if (score >= 5) results.push({ name:"Electrum", score, confidence: score>=6?"PROBABLE":"POSIBLE", signals:sigs });
|
if (score >= 5) results.push({ name:"Electrum", score, confidence: score>=6?"PROBABLE":"POSIBLE", signals:sigs });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Sin rasgos distintivos ────────────────────────────────────
|
||||||
|
// Una transacción sin locktime, sin RBF y con sequence al máximo no
|
||||||
|
// lleva ninguna de las marcas que dejan los monederos actuales. Decirlo
|
||||||
|
// es más honesto que forzar un nombre: es lo que produce una librería
|
||||||
|
// en crudo o un script propio, y también algún wallet antiguo.
|
||||||
|
{
|
||||||
|
const sinLocktime = locktime === 0;
|
||||||
|
const sinRbf = !hasRbfOptin;
|
||||||
|
const seqPorDefecto = allInputs.length > 0 && allInputs.every(v=>v.sequence===0xffffffff);
|
||||||
|
if (sinLocktime && sinRbf && seqPorDefecto && results.length === 0) {
|
||||||
|
results.push({
|
||||||
|
name: "sin rasgos distintivos", score: 3, confidence: "POSIBLE",
|
||||||
|
signals: ["locktime=0", "sin RBF", "sequence=0xFFFFFFFF"],
|
||||||
|
note: "No lleva las marcas que dejan los monederos habituales. Compatible con una librería usada en crudo, un script propio o software antiguo — pero no identifica a ninguno en concreto.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── BlueWallet / mobile ───────────────────────────────────────
|
// ─── BlueWallet / mobile ───────────────────────────────────────
|
||||||
// Corregido: eliminado doble-conteo de "1 input P2WPKH" (antes sumaba 4 pts solo).
|
// Corregido: eliminado doble-conteo de "1 input P2WPKH" (antes sumaba 4 pts solo).
|
||||||
// Umbral subido a 6 para no dispararse con cualquier tx simple genérica.
|
// Umbral subido a 6 para no dispararse con cualquier tx simple genérica.
|
||||||
@@ -1631,6 +1660,48 @@
|
|||||||
});
|
});
|
||||||
if (hasInputReuse) deductions += weights.input_reuse;
|
if (hasInputReuse) deductions += weights.input_reuse;
|
||||||
|
|
||||||
|
// ── 1b. Vinculación de direcciones al gastarlas juntas (CIOH) ──────
|
||||||
|
// Faltaba, y es el hueco más grande que tenía el analizador: se medía
|
||||||
|
// la privacidad desde el lado de quien CONSTRUYE la transacción (¿se ve
|
||||||
|
// mi cambio?, ¿pago cifras redondas?) y no desde el lado de los dueños
|
||||||
|
// de las monedas gastadas, que es donde ocurre el daño irreversible.
|
||||||
|
//
|
||||||
|
// Gastar N direcciones distintas en una misma transacción las enlaza
|
||||||
|
// para siempre a ojos de cualquiera. Es la heurística CIOH, la más
|
||||||
|
// fiable del análisis de cadena, y aquí no se aplicaba a la propia
|
||||||
|
// transacción que se está mirando — solo al conjunto del wallet.
|
||||||
|
//
|
||||||
|
// Se detectó analizando una consolidación real de 491 direcciones: la
|
||||||
|
// app la calificaba de privacidad ALTA porque el cambio no se
|
||||||
|
// distinguía, sin mencionar que 491 direcciones acababan de quedar
|
||||||
|
// vinculadas en público.
|
||||||
|
//
|
||||||
|
// No aplica en CoinJoin: ahí las entradas son de personas distintas y
|
||||||
|
// esa es justamente la excepción clásica a CIOH.
|
||||||
|
const nVinculadas = uniqueInputAddrs.size;
|
||||||
|
const hayVinculacion = nVinculadas > 1 && !likelyCJ;
|
||||||
|
// Escalonado: consolidar dos monedas es rutina; juntar decenas es otra
|
||||||
|
// cosa. El peso sube con el número de direcciones que quedan atadas.
|
||||||
|
const pesoVinculacion = !hayVinculacion ? 0
|
||||||
|
: nVinculadas >= 20 ? 30
|
||||||
|
: nVinculadas >= 10 ? 22
|
||||||
|
: nVinculadas >= 5 ? 15
|
||||||
|
: 8;
|
||||||
|
checks.push({
|
||||||
|
id:"input_linkage", label:"Direcciones que quedan vinculadas entre sí", certainty:"CERTEZA",
|
||||||
|
pass: !hayVinculacion,
|
||||||
|
informational: likelyCJ && nVinculadas > 1,
|
||||||
|
actionability: "evitable",
|
||||||
|
detail: !hayVinculacion
|
||||||
|
? (likelyCJ && nVinculadas > 1
|
||||||
|
? `Se gastan ${nVinculadas} direcciones distintas, pero en un CoinJoin las entradas son de participantes diferentes: la vinculación por CIOH no aplica aquí. De hecho, romperla es el objetivo de la mezcla.`
|
||||||
|
: "Solo se gasta una dirección, así que esta transacción no enlaza direcciones entre sí.")
|
||||||
|
: `Hecho (certeza): esta transacción gasta ${nVinculadas} direcciones distintas a la vez. Interpretación (certeza para un observador): quien mira la cadena asume que las ${nVinculadas} pertenecen al mismo dueño, porque hizo falta la clave de todas para firmar. Consecuencia: quedan enlazadas de forma permanente y pública. Si alguna de ellas se asocia alguna vez a una identidad, arrastra a las demás — y esto no se puede deshacer.`,
|
||||||
|
didactic: "Es la heurística CIOH (common input ownership), la más fiable que existe en el análisis de cadena y la primera que aplica cualquiera que observe. Cada vez que gastas varias monedas juntas, le dices al mundo que son de la misma persona. Consolidar cuando las comisiones están baratas es tentador y sale caro en privacidad: media docena de consolidaciones descuidadas bastan para reconstruir un wallet entero. La forma de evitarlo es gastar monedas de una en una cuando su origen no debe relacionarse — que es justo lo contrario de lo que invita a hacer una comisión baja.",
|
||||||
|
penalty: pesoVinculacion,
|
||||||
|
});
|
||||||
|
if (hayVinculacion) deductions += pesoVinculacion;
|
||||||
|
|
||||||
// ── 2. Mezcla de tipos en inputs ──────────────────────────────────
|
// ── 2. Mezcla de tipos en inputs ──────────────────────────────────
|
||||||
const inTypes = [...new Set(tx.vin.map(v=>v.prevout?.scriptpubkey_type).filter(Boolean))];
|
const inTypes = [...new Set(tx.vin.map(v=>v.prevout?.scriptpubkey_type).filter(Boolean))];
|
||||||
const outTypes = [...new Set(tx.vout.map(v=>v.scriptpubkey_type).filter(Boolean))];
|
const outTypes = [...new Set(tx.vout.map(v=>v.scriptpubkey_type).filter(Boolean))];
|
||||||
@@ -1884,20 +1955,25 @@
|
|||||||
checks.push({
|
checks.push({
|
||||||
id:"wallet_fingerprint", label:"Fingerprinting de wallet",
|
id:"wallet_fingerprint", label:"Fingerprinting de wallet",
|
||||||
certainty: detectedWallets[0]?.confidence||"POSIBLE",
|
certainty: detectedWallets[0]?.confidence||"POSIBLE",
|
||||||
pass: likelyCJ ? true : detectedWallets.length===0,
|
pass: likelyCJ ? true : !detectedWallets.some(w => w.name !== "sin rasgos distintivos"),
|
||||||
informational: likelyCJ && detectedWallets.length > 0,
|
informational: (likelyCJ && detectedWallets.length > 0) ||
|
||||||
|
(detectedWallets.length > 0 && !detectedWallets.some(w => w.name !== "sin rasgos distintivos")),
|
||||||
actionability: likelyCJ ? null : "wallet",
|
actionability: likelyCJ ? null : "wallet",
|
||||||
detail: detectedWallets.length > 0
|
detail: detectedWallets.length > 0
|
||||||
? (likelyCJ
|
? (likelyCJ
|
||||||
? `En un CoinJoin identificar el software es informativo, no un problema. ${detectedWallets.map(w=>`${w.name} (${w.confidence}): ${w.signals.join(", ")}`).join(" · ")}`
|
? `En un CoinJoin identificar el software es informativo, no un problema. ${detectedWallets.map(w=>`${w.name} (${w.confidence}): ${w.signals.join(", ")}`).join(" · ")}`
|
||||||
: detectedWallets.map(w=>`${w.name} (${w.confidence}): ${w.signals.join(", ")}`).join(" · "))
|
: detectedWallets.map(w=>`${w.name} (${w.confidence}): ${w.signals.join(", ")}${w.note?` — ${w.note}`:""}`).join(" · "))
|
||||||
: "No se detecta un patrón de wallet específico con suficiente certeza.",
|
: "No se detecta un patrón de wallet específico con suficiente certeza.",
|
||||||
didactic: "El objetivo no es ser invisible sino ser indistinguible de millones de usuarios del mismo wallet. Un fingerprint de Bitcoin Core lo comparten millones de transacciones — no revela nada útil. Un fingerprint de Exodus o un wallet minoritario pertenece a un conjunto mucho menor y es más revelador. En un CoinJoin, el tipo de mezcla ya sugiere el software usado.",
|
didactic: "El objetivo no es ser invisible sino ser indistinguible de millones de usuarios del mismo wallet. Un fingerprint de Bitcoin Core lo comparten millones de transacciones — no revela nada útil. Un fingerprint de Exodus o un wallet minoritario pertenece a un conjunto mucho menor y es más revelador. En un CoinJoin, el tipo de mezcla ya sugiere el software usado.",
|
||||||
penalty: likelyCJ ? 0 : weights.wallet_fingerprint,
|
penalty: likelyCJ ? 0 : weights.wallet_fingerprint,
|
||||||
});
|
});
|
||||||
// En CoinJoin no penaliza (identificar el wallet es esperado, no un fallo).
|
// En CoinJoin no penaliza (identificar el wallet es esperado, no un fallo).
|
||||||
// Fuera de CoinJoin, solo penaliza si se detectó un wallet estructural real.
|
// Fuera de CoinJoin, solo penaliza si se identificó un software concreto:
|
||||||
if (!likelyCJ && detectedWallets.length > 0) deductions += weights.wallet_fingerprint;
|
// "sin rasgos distintivos" es lo contrario de una huella — significa que
|
||||||
|
// la transacción no se puede atribuir a ningún monedero, que si acaso
|
||||||
|
// juega a favor. Penalizarlo sería cobrar por no dejar rastro.
|
||||||
|
const huellaReal = detectedWallets.filter(w => w.name !== "sin rasgos distintivos");
|
||||||
|
if (!likelyCJ && huellaReal.length > 0) deductions += weights.wallet_fingerprint;
|
||||||
|
|
||||||
// ── 13. PayJoin — informativo ─────────────────────────────────────
|
// ── 13. PayJoin — informativo ─────────────────────────────────────
|
||||||
checks.push({
|
checks.push({
|
||||||
@@ -3813,6 +3889,13 @@
|
|||||||
const funded = stats.funded_txo_sum || 0;
|
const funded = stats.funded_txo_sum || 0;
|
||||||
const spent = stats.spent_txo_sum || 0;
|
const spent = stats.spent_txo_sum || 0;
|
||||||
const txCount = stats.tx_count || 0;
|
const txCount = stats.tx_count || 0;
|
||||||
|
// El backend de Mempool sobre Fulcrum devuelve los importes a cero en
|
||||||
|
// direcciones con mucho historial, aunque el contador de transacciones
|
||||||
|
// sí venga bien. Una dirección con transacciones pero sin nada recibido
|
||||||
|
// es imposible en la cadena: si se da, los datos están incompletos.
|
||||||
|
// Sin esta comprobación, el perfil concluye "no es hot wallet" cuando en
|
||||||
|
// realidad no ha podido mirarlo — otro silencio leído como respuesta.
|
||||||
|
const statsIncompletas = txCount > 0 && (stats.funded_txo_count || 0) === 0;
|
||||||
const residual = funded - spent;
|
const residual = funded - spent;
|
||||||
const residualRatio = funded > 0 ? residual / funded : 0;
|
const residualRatio = funded > 0 ? residual / funded : 0;
|
||||||
|
|
||||||
@@ -3822,7 +3905,9 @@
|
|||||||
// Señal de flujo: saldo residual casi nulo + volumen y tx_count altos ->
|
// Señal de flujo: saldo residual casi nulo + volumen y tx_count altos ->
|
||||||
// la dirección funciona como paso de caudal, no como ahorro (típico de
|
// la dirección funciona como paso de caudal, no como ahorro (típico de
|
||||||
// hot wallet de exchange). Lo contrario -> retención típica de uso personal.
|
// hot wallet de exchange). Lo contrario -> retención típica de uso personal.
|
||||||
if (funded > 0 && residualRatio < 0.05 && txCount >= 20) {
|
if (statsIncompletas) {
|
||||||
|
signals.push(`el nodo no devolvió los importes de esta dirección (${txCount} transacciones registradas, 0 recibido — imposible en la cadena). La señal de flujo no se ha podido evaluar: no es que se haya descartado, es que no se ha podido mirar`);
|
||||||
|
} else if (funded > 0 && residualRatio < 0.05 && txCount >= 20) {
|
||||||
hotScore += 2;
|
hotScore += 2;
|
||||||
signals.push(`saldo residual ≈0 (${(residualRatio*100).toFixed(1)}% de lo recibido) con ${txCount} transacciones — patrón de flujo, no de ahorro`);
|
signals.push(`saldo residual ≈0 (${(residualRatio*100).toFixed(1)}% de lo recibido) con ${txCount} transacciones — patrón de flujo, no de ahorro`);
|
||||||
} else if (funded > 0 && residualRatio > 0.3 && txCount < 10) {
|
} else if (funded > 0 && residualRatio > 0.3 && txCount < 10) {
|
||||||
@@ -5797,18 +5882,30 @@
|
|||||||
const m=addr.mempool_stats||{funded_txo_sum:0,spent_txo_sum:0};
|
const m=addr.mempool_stats||{funded_txo_sum:0,spent_txo_sum:0};
|
||||||
const balance=c.funded_txo_sum-c.spent_txo_sum;
|
const balance=c.funded_txo_sum-c.spent_txo_sum;
|
||||||
const unconf=(m.funded_txo_sum||0)-(m.spent_txo_sum||0);
|
const unconf=(m.funded_txo_sum||0)-(m.spent_txo_sum||0);
|
||||||
|
// Con Fulcrum por debajo, el backend devuelve los importes a cero en
|
||||||
|
// direcciones con mucho historial, aunque el contador de transacciones
|
||||||
|
// venga bien. Tener transacciones y no haber recibido nada es imposible
|
||||||
|
// en la cadena, así que cuando se da, los importes no son reales: son un
|
||||||
|
// hueco. Mostrar "Balance 0" tal cual sería mentir con seguridad.
|
||||||
|
const importesIncompletos = c.tx_count > 0 && (c.funded_txo_count||0) === 0;
|
||||||
|
const noDisponible = "no disponible";
|
||||||
return (
|
return (
|
||||||
<div style={{display:"flex",flexDirection:"column",gap:10}}>
|
<div style={{display:"flex",flexDirection:"column",gap:10}}>
|
||||||
<Card glow={C.green}>
|
<Card glow={C.green}>
|
||||||
<div style={{marginBottom:12}}><div style={{fontSize:"0.6rem",color:C.t2,fontFamily:"monospace",marginBottom:4}}>DIRECCIÓN</div><div style={{fontSize:"0.72rem",fontFamily:"monospace",color:C.green,wordBreak:"break-all"}}>{addr.address}</div></div>
|
<div style={{marginBottom:12}}><div style={{fontSize:"0.6rem",color:C.t2,fontFamily:"monospace",marginBottom:4}}>DIRECCIÓN</div><div style={{fontSize:"0.72rem",fontFamily:"monospace",color:C.green,wordBreak:"break-all"}}>{addr.address}</div></div>
|
||||||
<div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(130px,1fr))",gap:12,paddingTop:12,borderTop:`1px solid ${C.border}`}}>
|
<div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(130px,1fr))",gap:12,paddingTop:12,borderTop:`1px solid ${C.border}`}}>
|
||||||
<Tag label="Balance" value={fmt.btc(balance)} color={C.green}/>
|
<Tag label="Balance" value={importesIncompletos?noDisponible:fmt.btc(balance)} color={importesIncompletos?C.t3:C.green}/>
|
||||||
<Tag label="No confirmado" value={unconf!==0?fmt.btc(unconf):"0"} color={unconf>0?C.amber:C.t2}/>
|
<Tag label="No confirmado" value={unconf!==0?fmt.btc(unconf):"0"} color={unconf>0?C.amber:C.t2}/>
|
||||||
<Tag label="Total recibido" value={fmt.btc(c.funded_txo_sum)} color={C.blue}/>
|
<Tag label="Total recibido" value={importesIncompletos?noDisponible:fmt.btc(c.funded_txo_sum)} color={importesIncompletos?C.t3:C.blue}/>
|
||||||
<Tag label="Total enviado" value={fmt.btc(c.spent_txo_sum)} color={C.red}/>
|
<Tag label="Total enviado" value={importesIncompletos?noDisponible:fmt.btc(c.spent_txo_sum)} color={importesIncompletos?C.t3:C.red}/>
|
||||||
<Tag label="Transacciones" value={fmt.num(c.tx_count)} color={C.t1}/>
|
<Tag label="Transacciones" value={fmt.num(c.tx_count)} color={C.t1}/>
|
||||||
<Tag label="UTXOs" value={fmt.num(addr.utxos?addr.utxos.length:0)} color={C.amber}/>
|
<Tag label="UTXOs" value={fmt.num(addr.utxos?addr.utxos.length:0)} color={C.amber}/>
|
||||||
</div>
|
</div>
|
||||||
|
{importesIncompletos&&(
|
||||||
|
<div style={{marginTop:12,padding:"9px 12px",background:C.amberMuted,border:`1px solid ${C.amber}40`,borderRadius:6,fontSize:"0.63rem",color:C.t1,fontFamily:"monospace",lineHeight:1.6}}>
|
||||||
|
<strong style={{color:C.amber}}>Importes no disponibles.</strong> El nodo informa de {fmt.num(c.tx_count)} transacciones pero devuelve cero recibido, y eso no puede ocurrir en la cadena. Es una limitación conocida del backend de Mempool sobre Fulcrum con direcciones de mucho historial. Los importes se ocultan en vez de mostrarse a cero, porque un cero aquí sería un dato falso. El número de transacciones y la lista de abajo sí son correctos.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
{analysis&&<PrivacyLab analysis={analysis}/>}
|
{analysis&&<PrivacyLab analysis={analysis}/>}
|
||||||
{addr.utxos&&addr.utxos.length>0&&<Card><div style={{fontSize:"0.62rem",color:C.t2,fontFamily:"monospace",textTransform:"uppercase",letterSpacing:"0.1em",marginBottom:10}}>UTXOs</div>{addr.utxos.slice(0,6).map((u,i)=><div key={i} style={{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"6px 0",borderBottom:i<Math.min(addr.utxos.length,6)-1?`1px solid ${C.border}`:"none"}}><span style={{fontSize:"0.68rem",fontFamily:"monospace",color:C.blue}}>{fmt.hash(u.txid)}:{u.vout}</span><div style={{display:"flex",gap:6}}><Badge color={C.green}>{fmt.btc(u.value)}</Badge><Badge color={u.status&&u.status.confirmed?C.t2:C.amber}>{u.status&&u.status.confirmed?`#${fmt.num(u.status.block_height)}`:"mempool"}</Badge></div></div>)}</Card>}
|
{addr.utxos&&addr.utxos.length>0&&<Card><div style={{fontSize:"0.62rem",color:C.t2,fontFamily:"monospace",textTransform:"uppercase",letterSpacing:"0.1em",marginBottom:10}}>UTXOs</div>{addr.utxos.slice(0,6).map((u,i)=><div key={i} style={{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"6px 0",borderBottom:i<Math.min(addr.utxos.length,6)-1?`1px solid ${C.border}`:"none"}}><span style={{fontSize:"0.68rem",fontFamily:"monospace",color:C.blue}}>{fmt.hash(u.txid)}:{u.vout}</span><div style={{display:"flex",gap:6}}><Badge color={C.green}>{fmt.btc(u.value)}</Badge><Badge color={u.status&&u.status.confirmed?C.t2:C.amber}>{u.status&&u.status.confirmed?`#${fmt.num(u.status.block_height)}`:"mempool"}</Badge></div></div>)}</Card>}
|
||||||
|
|||||||
Reference in New Issue
Block a user