Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -7,13 +7,13 @@ import re
|
|
| 7 |
import os
|
| 8 |
import tempfile
|
| 9 |
import base64
|
| 10 |
-
|
| 11 |
|
| 12 |
# ---------------- Funciones de análisis y grafo -------------------
|
| 13 |
|
| 14 |
def get_transaction(tx_id):
|
| 15 |
"""
|
| 16 |
-
Obtiene datos de una transacción Bitcoin usando endpoints públicos.
|
| 17 |
Se prueba primero con Blockstream.info y, de fallar, con mempool.space.
|
| 18 |
"""
|
| 19 |
urls = [
|
|
@@ -71,7 +71,7 @@ def check_blockchain_tags(tx_id):
|
|
| 71 |
"""
|
| 72 |
Consulta la API pública de blockchain.com para ver si el TXID (o sus metadatos)
|
| 73 |
indica que la transacción ha sido etiquetada como MIXER.
|
| 74 |
-
(Esta función es un ejemplo y
|
| 75 |
"""
|
| 76 |
url = f"https://blockchain.info/rawtx/{tx_id}?format=json"
|
| 77 |
try:
|
|
@@ -96,14 +96,13 @@ def analizar_transaccion(tx_id):
|
|
| 96 |
- Calcula totales en BTC y fee, mostrando también su equivalente en USD.
|
| 97 |
- Muestra información adicional (versión, tamaño, peso, fee rate).
|
| 98 |
- Aplica heurística para detectar posibles CoinJoin/mixers.
|
| 99 |
-
- Incorpora información de blockchain.com (etiqueta MIXER).
|
| 100 |
- Genera un grafo interactivo.
|
| 101 |
-
- Retorna un informe en HTML, la figura del grafo y una tupla (reporte, fig)
|
| 102 |
"""
|
| 103 |
tx_data = get_transaction(tx_id)
|
| 104 |
if not tx_data:
|
| 105 |
return ("❌ Transacción no encontrada o error al obtener datos. Asegúrate de ingresar un TXID válido."), None, None
|
| 106 |
-
|
| 107 |
try:
|
| 108 |
num_inputs = len(tx_data.get("vin", []))
|
| 109 |
num_outputs = len(tx_data.get("vout", []))
|
|
@@ -113,7 +112,6 @@ def analizar_transaccion(tx_id):
|
|
| 113 |
total_output_value = sum(out.get("value", 0) for out in tx_data.get("vout", [])) / 1e8
|
| 114 |
fee = total_input_value - total_output_value
|
| 115 |
|
| 116 |
-
# Heurística para detectar mixer
|
| 117 |
heuristic_mixer = ((num_inputs > 5 and num_outputs > 5 and montos_unicos < 3) or
|
| 118 |
(num_outputs > 2 and montos_unicos <= 2))
|
| 119 |
es_mixer_blockchain = check_blockchain_tags(tx_id)
|
|
@@ -247,23 +245,35 @@ def analizar_transaccion(tx_id):
|
|
| 247 |
</p>
|
| 248 |
</div>
|
| 249 |
"""
|
| 250 |
-
# Se devuelve además la tupla (reporte, fig) para almacenarla en el estado.
|
| 251 |
return reporte, fig, (reporte, fig)
|
| 252 |
|
| 253 |
except Exception as e:
|
| 254 |
return f"⚠️ Error durante el análisis: {str(e)}", None, None
|
| 255 |
|
| 256 |
-
# ---------------- Función para mostrar el modal -------------------
|
| 257 |
|
| 258 |
def mostrar_modal(analysis_tuple):
|
| 259 |
"""
|
| 260 |
-
|
| 261 |
-
abrir el modal y el contenido HTML del informe.
|
| 262 |
"""
|
| 263 |
if not analysis_tuple:
|
| 264 |
-
return
|
| 265 |
report, _ = analysis_tuple
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
|
| 268 |
# ---------------- INTERFAZ GRÁFICA CON GRADIO -------------------
|
| 269 |
|
|
@@ -278,7 +288,7 @@ with gr.Blocks(
|
|
| 278 |
gr.Markdown("**Nota:** Este analizador funciona únicamente con transacciones de Bitcoin. No se pueden analizar transacciones de Ethereum.")
|
| 279 |
|
| 280 |
with gr.Row():
|
| 281 |
-
# Columna izquierda: TXID, Ejemplos y Explicación
|
| 282 |
with gr.Column(scale=1):
|
| 283 |
tx_input = gr.Textbox(
|
| 284 |
label="TXID de la Transacción",
|
|
@@ -292,7 +302,6 @@ with gr.Blocks(
|
|
| 292 |
],
|
| 293 |
inputs=tx_input
|
| 294 |
)
|
| 295 |
-
# Recuadro de explicación (scroll ampliado a 350px)
|
| 296 |
explanation_box = gr.HTML(value="""
|
| 297 |
<div style="overflow-y: auto; height: 350px; border: 1px solid #cccccc; padding: 10px;">
|
| 298 |
<h4>Explicación de los campos:</h4>
|
|
@@ -315,24 +324,20 @@ with gr.Blocks(
|
|
| 315 |
</ul>
|
| 316 |
</div>
|
| 317 |
""")
|
| 318 |
-
# Columna derecha: Grafo y
|
| 319 |
with gr.Column(scale=1):
|
| 320 |
plot_output = gr.Plot()
|
| 321 |
reporte_html = gr.HTML()
|
| 322 |
analyze_btn = gr.Button("Analizar Transacción", elem_classes=["custom-btn"])
|
|
|
|
|
|
|
| 323 |
|
| 324 |
# Estado para almacenar el análisis (tupla: (reporte, fig))
|
| 325 |
analysis_state = gr.State()
|
| 326 |
|
| 327 |
-
# Al hacer clic en "Analizar Transacción", se
|
| 328 |
analyze_btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output, analysis_state])
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
modal_btn = gr.Button("GENERAR ANALISIS", elem_classes=["custom-btn"])
|
| 332 |
-
with gr.Modal("Resultados del Análisis", visible=False) as result_modal:
|
| 333 |
-
modal_report = gr.HTML()
|
| 334 |
-
|
| 335 |
-
# La función mostrar_modal abrirá el modal con el contenido del análisis.
|
| 336 |
-
modal_btn.click(fn=mostrar_modal, inputs=analysis_state, outputs=[result_modal, modal_report])
|
| 337 |
|
| 338 |
demo.launch()
|
|
|
|
| 7 |
import os
|
| 8 |
import tempfile
|
| 9 |
import base64
|
| 10 |
+
# No se usan fpdf en este ejemplo
|
| 11 |
|
| 12 |
# ---------------- Funciones de análisis y grafo -------------------
|
| 13 |
|
| 14 |
def get_transaction(tx_id):
|
| 15 |
"""
|
| 16 |
+
Obtiene los datos de una transacción Bitcoin usando endpoints públicos.
|
| 17 |
Se prueba primero con Blockstream.info y, de fallar, con mempool.space.
|
| 18 |
"""
|
| 19 |
urls = [
|
|
|
|
| 71 |
"""
|
| 72 |
Consulta la API pública de blockchain.com para ver si el TXID (o sus metadatos)
|
| 73 |
indica que la transacción ha sido etiquetada como MIXER.
|
| 74 |
+
(Esta función es un ejemplo y podría necesitar ajustes según la respuesta real.)
|
| 75 |
"""
|
| 76 |
url = f"https://blockchain.info/rawtx/{tx_id}?format=json"
|
| 77 |
try:
|
|
|
|
| 96 |
- Calcula totales en BTC y fee, mostrando también su equivalente en USD.
|
| 97 |
- Muestra información adicional (versión, tamaño, peso, fee rate).
|
| 98 |
- Aplica heurística para detectar posibles CoinJoin/mixers.
|
| 99 |
+
- Incorpora información adicional de blockchain.com (etiqueta MIXER).
|
| 100 |
- Genera un grafo interactivo.
|
| 101 |
+
- Retorna un informe en HTML, la figura del grafo y una tupla (reporte, fig) para su uso.
|
| 102 |
"""
|
| 103 |
tx_data = get_transaction(tx_id)
|
| 104 |
if not tx_data:
|
| 105 |
return ("❌ Transacción no encontrada o error al obtener datos. Asegúrate de ingresar un TXID válido."), None, None
|
|
|
|
| 106 |
try:
|
| 107 |
num_inputs = len(tx_data.get("vin", []))
|
| 108 |
num_outputs = len(tx_data.get("vout", []))
|
|
|
|
| 112 |
total_output_value = sum(out.get("value", 0) for out in tx_data.get("vout", [])) / 1e8
|
| 113 |
fee = total_input_value - total_output_value
|
| 114 |
|
|
|
|
| 115 |
heuristic_mixer = ((num_inputs > 5 and num_outputs > 5 and montos_unicos < 3) or
|
| 116 |
(num_outputs > 2 and montos_unicos <= 2))
|
| 117 |
es_mixer_blockchain = check_blockchain_tags(tx_id)
|
|
|
|
| 245 |
</p>
|
| 246 |
</div>
|
| 247 |
"""
|
|
|
|
| 248 |
return reporte, fig, (reporte, fig)
|
| 249 |
|
| 250 |
except Exception as e:
|
| 251 |
return f"⚠️ Error durante el análisis: {str(e)}", None, None
|
| 252 |
|
| 253 |
+
# ---------------- Función para mostrar el modal mediante HTML -------------------
|
| 254 |
|
| 255 |
def mostrar_modal(analysis_tuple):
|
| 256 |
"""
|
| 257 |
+
A partir de la tupla de análisis, devuelve un HTML con un modal emergente que contiene el informe.
|
|
|
|
| 258 |
"""
|
| 259 |
if not analysis_tuple:
|
| 260 |
+
return "<p>No hay análisis generado.</p>"
|
| 261 |
report, _ = analysis_tuple
|
| 262 |
+
html_modal = f'''
|
| 263 |
+
<div id="myModal" style="
|
| 264 |
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
| 265 |
+
background: rgba(0,0,0,0.8); z-index: 9999;">
|
| 266 |
+
<div style="
|
| 267 |
+
position: absolute; top: 50%; left: 50%;
|
| 268 |
+
transform: translate(-50%, -50%);
|
| 269 |
+
background: white; padding: 20px; max-height: 90%; overflow-y: auto;">
|
| 270 |
+
{report}
|
| 271 |
+
<br><br><button onclick="document.getElementById('myModal').remove();"
|
| 272 |
+
style='padding: 5px 10px; font-size: 12px;'>Cerrar</button>
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
'''
|
| 276 |
+
return html_modal
|
| 277 |
|
| 278 |
# ---------------- INTERFAZ GRÁFICA CON GRADIO -------------------
|
| 279 |
|
|
|
|
| 288 |
gr.Markdown("**Nota:** Este analizador funciona únicamente con transacciones de Bitcoin. No se pueden analizar transacciones de Ethereum.")
|
| 289 |
|
| 290 |
with gr.Row():
|
| 291 |
+
# Columna izquierda: Campo de TXID, Ejemplos y Explicación de campos
|
| 292 |
with gr.Column(scale=1):
|
| 293 |
tx_input = gr.Textbox(
|
| 294 |
label="TXID de la Transacción",
|
|
|
|
| 302 |
],
|
| 303 |
inputs=tx_input
|
| 304 |
)
|
|
|
|
| 305 |
explanation_box = gr.HTML(value="""
|
| 306 |
<div style="overflow-y: auto; height: 350px; border: 1px solid #cccccc; padding: 10px;">
|
| 307 |
<h4>Explicación de los campos:</h4>
|
|
|
|
| 324 |
</ul>
|
| 325 |
</div>
|
| 326 |
""")
|
| 327 |
+
# Columna derecha: Grafo, Reporte y botón para mostrar modal con análisis completo
|
| 328 |
with gr.Column(scale=1):
|
| 329 |
plot_output = gr.Plot()
|
| 330 |
reporte_html = gr.HTML()
|
| 331 |
analyze_btn = gr.Button("Analizar Transacción", elem_classes=["custom-btn"])
|
| 332 |
+
modal_btn = gr.Button("GENERAR ANALISIS", elem_classes=["custom-btn"])
|
| 333 |
+
modal_output = gr.HTML()
|
| 334 |
|
| 335 |
# Estado para almacenar el análisis (tupla: (reporte, fig))
|
| 336 |
analysis_state = gr.State()
|
| 337 |
|
| 338 |
+
# Al hacer clic en "Analizar Transacción", se generan reporte, grafo y se guarda el análisis
|
| 339 |
analyze_btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output, analysis_state])
|
| 340 |
+
# Al hacer clic en "GENERAR ANALISIS", se muestra un modal emergente con el informe completo
|
| 341 |
+
modal_btn.click(fn=mostrar_modal, inputs=analysis_state, outputs=modal_output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
demo.launch()
|