leonett commited on
Commit
577c6bb
·
verified ·
1 Parent(s): e87d9f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -62
app.py CHANGED
@@ -6,78 +6,80 @@ from datetime import datetime
6
 
7
  def get_transaction(tx_id):
8
  """
9
- Obtiene los datos de una transacción usando la API pública de Blockstream.
10
- Retorna el JSON de la transacción si se obtiene correctamente, o None en caso de error.
11
  """
12
- try:
13
- url = f"https://blockstream.info/api/tx/{tx_id}"
14
- response = requests.get(url, timeout=10)
15
- if response.status_code == 200:
16
- return response.json()
17
- else:
18
- print(f"Error: Código de estado {response.status_code}")
19
- return None
20
- except Exception as e:
21
- print("Excepción en get_transaction:", e)
22
- return None
 
 
 
23
 
24
  def generar_grafo(tx_data):
25
  """
26
  Genera un grafo dirigido a partir de los datos de la transacción.
27
- - Nodo central: la transacción.
28
- - Nodos de entrada (inputs) y salida (outputs) conectados al nodo central.
29
  """
30
  G = nx.DiGraph()
31
 
32
  txid = tx_data.get("txid", "desconocido")
33
- G.add_node(txid, color="#FF6B6B", size=20, tipo="tx")
34
 
35
- # Agregar inputs (direcciones anteriores)
36
  for inp in tx_data.get("vin", []):
37
  prevout = inp.get("prevout", {})
38
  addr = prevout.get("scriptpubkey_address")
39
  if addr:
40
- G.add_node(addr, color="#4ECDC4", size=15, tipo="input")
41
  G.add_edge(addr, txid)
42
 
43
- # Agregar outputs (direcciones destino)
44
  for out in tx_data.get("vout", []):
45
  addr = out.get("scriptpubkey_address")
46
  if addr:
47
- G.add_node(addr, color="#45B7D1", size=15, tipo="output")
48
  G.add_edge(txid, addr)
49
 
50
  return G
51
 
52
  def analizar_transaccion(tx_id):
53
  """
54
- Analiza una transacción BTC:
55
- - Obtiene los datos de la transacción.
56
  - Aplica una heurística simple para detectar posibles mixers.
57
- - Genera un grafo interactivo con Plotly.
58
- - Retorna un reporte en HTML junto con la figura del grafo.
 
59
  """
60
  tx_data = get_transaction(tx_id)
61
  if not tx_data:
62
- return "❌ Transacción no encontrada o error al obtener datos.", None
 
63
 
64
  try:
65
- # Heurística para detectar mixer
66
  num_inputs = len(tx_data.get("vin", []))
67
  num_outputs = len(tx_data.get("vout", []))
68
  montos = [out.get("value", 0) / 1e8 for out in tx_data.get("vout", [])]
69
  montos_unicos = len(set(montos))
70
 
71
- # Umbrales de detección (se pueden ajustar)
72
  es_mixer = num_inputs > 5 and num_outputs > 5 and montos_unicos < 3
73
 
74
- # Generar grafo de la transacción
75
  G = generar_grafo(tx_data)
76
-
77
- # Calcular posición de los nodos (seed para consistencia)
78
  pos = nx.spring_layout(G, seed=42)
79
 
80
- # Preparar coordenadas para las aristas
81
  edge_x, edge_y = [], []
82
  for edge in G.edges():
83
  x0, y0 = pos[edge[0]]
@@ -85,19 +87,17 @@ def analizar_transaccion(tx_id):
85
  edge_x.extend([x0, x1, None])
86
  edge_y.extend([y0, y1, None])
87
 
88
- # Preparar datos de los nodos
89
- node_x, node_y = [], []
90
- hover_texts, node_colors, node_sizes = [], [], []
91
  for node in G.nodes():
92
  x, y = pos[node]
93
  node_x.append(x)
94
  node_y.append(y)
95
  tipo = G.nodes[node].get("tipo", "desconocido")
96
- hover_texts.append(f"Tipo: {tipo}<br>ID: {node[:6]}...")
97
  node_colors.append(G.nodes[node].get("color", "#FFFFFF"))
98
  node_sizes.append(G.nodes[node].get("size", 10))
99
 
100
- # Crear figura interactiva con Plotly
101
  fig = go.Figure(
102
  data=[
103
  go.Scatter(
@@ -110,13 +110,13 @@ def analizar_transaccion(tx_id):
110
  x=node_x, y=node_y,
111
  mode="markers+text",
112
  marker=dict(color=node_colors, size=node_sizes, line_width=1),
113
- text=[node[:6] for node in G.nodes()],
114
  hovertext=hover_texts,
115
  hoverinfo="text"
116
  )
117
  ],
118
  layout=go.Layout(
119
- title="Grafo de Transacción",
120
  showlegend=False,
121
  margin=dict(b=0, l=0, r=0, t=40),
122
  xaxis=dict(showgrid=False, zeroline=False, visible=False),
@@ -125,49 +125,55 @@ def analizar_transaccion(tx_id):
125
  )
126
  )
127
 
128
- # Formatear la fecha del bloque si está disponible
129
- block_time = tx_data.get("status", {}).get("block_time")
130
  fecha_str = datetime.fromtimestamp(block_time).strftime('%Y-%m-%d') if block_time else "Desconocida"
 
 
131
 
132
- # Reporte en HTML con estilos
133
  reporte = f"""
134
  <div style="padding: 20px; background: #1a1a1a; border-radius: 10px; color: white;">
135
- <h3 style="color: {'#4CAF50' if es_mixer else '#FF5252'};">
136
- {'🔮 POSIBLE MIXER' if es_mixer else '✅ TRANSACCIÓN NORMAL'}
137
  </h3>
 
138
  <p>📥 Inputs: {num_inputs}</p>
139
  <p>📤 Outputs: {num_outputs}</p>
140
  <p>💰 Montos únicos: {montos_unicos}</p>
141
  <p>📅 Fecha: {fecha_str}</p>
 
142
  </div>
143
  """
144
 
145
  return reporte, fig
146
-
147
  except Exception as e:
148
  return f"⚠️ Error durante el análisis: {str(e)}", None
149
 
150
- # Interfaz de Gradio
151
- with gr.Blocks(theme=gr.themes.Soft(), title="Detector de Mixers + Grafo") as demo:
152
- gr.Markdown("# 🌐 Detector de Mixers con Grafo")
153
- gr.Markdown("Analiza transacciones BTC **sin API key** y visualiza conexiones.")
154
 
155
  with gr.Row():
156
- tx_input = gr.Textbox(label="ID de Transacción", placeholder="Ej: c86157c5...")
157
- btn = gr.Button("Analizar 🕵️‍♂️")
158
-
159
- with gr.Row():
160
- reporte_html = gr.HTML()
161
- plot_output = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output])
164
-
165
- gr.Examples(
166
- examples=[
167
- ["c86157c5d8f6a96d18d5a5f015a726f8e7a837d00cfe2a7dc4a68b2df671f9d7"],
168
- ["f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"]
169
- ],
170
- inputs=tx_input
171
- )
172
 
173
  demo.launch()
 
6
 
7
  def get_transaction(tx_id):
8
  """
9
+ Intenta obtener los datos de una transacción Bitcoin usando endpoints públicos.
10
+ Primero se prueba con Blockstream.info y, de fallar, con Mempool.space.
11
  """
12
+ urls = [
13
+ f"https://blockstream.info/api/tx/{tx_id}",
14
+ f"https://mempool.space/api/tx/{tx_id}"
15
+ ]
16
+ for url in urls:
17
+ try:
18
+ response = requests.get(url, timeout=10)
19
+ if response.status_code == 200:
20
+ return response.json()
21
+ else:
22
+ print(f"Error en {url}: Código de estado {response.status_code}")
23
+ except Exception as e:
24
+ print(f"Excepción al consultar {url}: {e}")
25
+ return None
26
 
27
  def generar_grafo(tx_data):
28
  """
29
  Genera un grafo dirigido a partir de los datos de la transacción.
30
+ Nodo central: la transacción.
31
+ Nodos secundarios: direcciones de inputs y outputs.
32
  """
33
  G = nx.DiGraph()
34
 
35
  txid = tx_data.get("txid", "desconocido")
36
+ G.add_node(txid, color="#FF6B6B", size=20, tipo="Transacción")
37
 
38
+ # Inputs
39
  for inp in tx_data.get("vin", []):
40
  prevout = inp.get("prevout", {})
41
  addr = prevout.get("scriptpubkey_address")
42
  if addr:
43
+ G.add_node(addr, color="#4ECDC4", size=15, tipo="Input")
44
  G.add_edge(addr, txid)
45
 
46
+ # Outputs
47
  for out in tx_data.get("vout", []):
48
  addr = out.get("scriptpubkey_address")
49
  if addr:
50
+ G.add_node(addr, color="#45B7D1", size=15, tipo="Output")
51
  G.add_edge(txid, addr)
52
 
53
  return G
54
 
55
  def analizar_transaccion(tx_id):
56
  """
57
+ Analiza una transacción Bitcoin:
58
+ - Obtiene los datos.
59
  - Aplica una heurística simple para detectar posibles mixers.
60
+ - Genera un grafo interactivo.
61
+ - Retorna un reporte en HTML y la figura del grafo.
62
+ Nota: Este analizador solo funciona con transacciones de Bitcoin.
63
  """
64
  tx_data = get_transaction(tx_id)
65
  if not tx_data:
66
+ return ("❌ Transacción no encontrada o error al obtener datos. "
67
+ "Asegúrate de ingresar un TXID de Bitcoin válido."), None
68
 
69
  try:
70
+ # Heurística para detectar mixer: muchos inputs/outputs y pocos montos únicos.
71
  num_inputs = len(tx_data.get("vin", []))
72
  num_outputs = len(tx_data.get("vout", []))
73
  montos = [out.get("value", 0) / 1e8 for out in tx_data.get("vout", [])]
74
  montos_unicos = len(set(montos))
75
 
 
76
  es_mixer = num_inputs > 5 and num_outputs > 5 and montos_unicos < 3
77
 
78
+ # Generar grafo
79
  G = generar_grafo(tx_data)
 
 
80
  pos = nx.spring_layout(G, seed=42)
81
 
82
+ # Datos de aristas
83
  edge_x, edge_y = [], []
84
  for edge in G.edges():
85
  x0, y0 = pos[edge[0]]
 
87
  edge_x.extend([x0, x1, None])
88
  edge_y.extend([y0, y1, None])
89
 
90
+ # Datos de nodos
91
+ node_x, node_y, hover_texts, node_colors, node_sizes = [], [], [], [], []
 
92
  for node in G.nodes():
93
  x, y = pos[node]
94
  node_x.append(x)
95
  node_y.append(y)
96
  tipo = G.nodes[node].get("tipo", "desconocido")
97
+ hover_texts.append(f"Tipo: {tipo}<br>ID: {node[:8]}...")
98
  node_colors.append(G.nodes[node].get("color", "#FFFFFF"))
99
  node_sizes.append(G.nodes[node].get("size", 10))
100
 
 
101
  fig = go.Figure(
102
  data=[
103
  go.Scatter(
 
110
  x=node_x, y=node_y,
111
  mode="markers+text",
112
  marker=dict(color=node_colors, size=node_sizes, line_width=1),
113
+ text=[node[:8] for node in G.nodes()],
114
  hovertext=hover_texts,
115
  hoverinfo="text"
116
  )
117
  ],
118
  layout=go.Layout(
119
+ title="Visualización de Conexiones de la Transacción",
120
  showlegend=False,
121
  margin=dict(b=0, l=0, r=0, t=40),
122
  xaxis=dict(showgrid=False, zeroline=False, visible=False),
 
125
  )
126
  )
127
 
128
+ status = tx_data.get("status", {})
129
+ block_time = status.get("block_time")
130
  fecha_str = datetime.fromtimestamp(block_time).strftime('%Y-%m-%d') if block_time else "Desconocida"
131
+ confirmed = status.get("confirmed", False)
132
+ estado_confirmacion = "Confirmada" if confirmed else "No confirmada"
133
 
 
134
  reporte = f"""
135
  <div style="padding: 20px; background: #1a1a1a; border-radius: 10px; color: white;">
136
+ <h3 style="color: {'#FF5252' if es_mixer else '#4CAF50'};">
137
+ {'🔮 POSIBLE MIXER' if es_mixer else '✅ Transacción Normal'}
138
  </h3>
139
+ <p><strong>TXID:</strong> {tx_id}</p>
140
  <p>📥 Inputs: {num_inputs}</p>
141
  <p>📤 Outputs: {num_outputs}</p>
142
  <p>💰 Montos únicos: {montos_unicos}</p>
143
  <p>📅 Fecha: {fecha_str}</p>
144
+ <p>🔔 Estado: {estado_confirmacion}</p>
145
  </div>
146
  """
147
 
148
  return reporte, fig
149
+
150
  except Exception as e:
151
  return f"⚠️ Error durante el análisis: {str(e)}", None
152
 
153
+ with gr.Blocks(theme=gr.themes.Soft(), title="🔍 Detector de Mixers en Transacciones Bitcoin") as demo:
154
+ gr.Markdown("# 🔍 Detector de Mixers en Transacciones Bitcoin")
155
+ gr.Markdown("**Nota:** Este analizador funciona únicamente con transacciones de Bitcoin. "
156
+ "No se pueden analizar transacciones de Ethereum.")
157
 
158
  with gr.Row():
159
+ # Columna Izquierda: Entrada y Ejemplos
160
+ with gr.Column(scale=1):
161
+ tx_input = gr.Textbox(label="TXID de la Transacción",
162
+ placeholder="Ej: f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16")
163
+ btn = gr.Button("Analizar Transacción")
164
+ gr.Markdown("### Ejemplos de TXIDs válidos:")
165
+ gr.Examples(
166
+ examples=[
167
+ ["f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"],
168
+ ["7b67ce16ad1fd3b58030ebf5a5c21d41ef245a8f9f89db46d7f7df1ac857c2c7"]
169
+ ],
170
+ inputs=tx_input
171
+ )
172
+ # Columna Derecha: Resultados
173
+ with gr.Column(scale=2):
174
+ reporte_html = gr.HTML()
175
+ plot_output = gr.Plot()
176
 
177
  btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output])
 
 
 
 
 
 
 
 
178
 
179
  demo.launch()