Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lazer/cardano/pyth-guard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PYTH_API_KEY=RFta6yGNyTZLm1D2NTQ1XfYuslTBFBGahWe-cardano
PYTH_PREPROD_POLICY_ID=d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6
NETWORK=Cardano PreProd
PYTH_LAZER_WS_URL=wss://api.pyth.network/ws
STOP_LOSS_THRESHOLD=0.35
ADA_USD_FEED_ID=0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f

# Supabase — PythGuard Project
VITE_SUPABASE_URL=https://tpsyugoddcmkvyfhddqo.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRwc3l1Z29kZGNta3Z5ZmhkZHFvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQyMTEwMjAsImV4cCI6MjA4OTc4NzAyMH0.opXaTc14Ng-H19cClDD6SX486Q-5vqkLKgAfNZR2ZQ0
8 changes: 8 additions & 0 deletions lazer/cardano/pyth-guard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.env
node_modules/
dist/
.DS_Store
*.js.map
venv/
.venv/
build/
35 changes: 35 additions & 0 deletions lazer/cardano/pyth-guard/contracts-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# PythGuard — contracts-python

Implementación alternativa del smart contract de stop-loss usando **OpShin** y **PyCardano**.

## Setup

```bash
cd contracts-python
python -m venv venv
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate

pip install -r requirements.txt
```

## Uso

```bash
python main.py
```

Esto:
1. Compila `contract.py` a Plutus script using OpShin
2. Consulta el precio ADA/USD en Pyth Hermes API
3. Simula la validación de la transacción Cardano

## Archivos

| File | Description |
|---|---|
| `contract.py` | Smart contract OpShin (Python → Plutus) |
| `main.py` | Orchestrator: precio feed + compilación + TX simulada |
| `requirements.txt` | Dependencias Python |
26 changes: 26 additions & 0 deletions lazer/cardano/pyth-guard/contracts-python/contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from opshin.prelude import *

@datum
class VentaProtegida(Datum):
vendedor: PubKeyHash
precio_minimo: int # Precio base en USD (ej: 500000 para $0.50)
pyth_script_id: bytes # Hash del validador de Pyth (Mainnet/Preprod)

def validator(datum: VentaProtegida, redeemer: int, context: ScriptContext) -> None:
tx_info = context.tx_info

# 1. Verificar firma del dueño
assert datum.vendedor in tx_info.signatories, "Firma del vendedor requerida"

# 2. Verificar que Pyth validó el precio en esta Tx
# En Cardano, Pyth usa un 'withdrawal' (retiro) de 0 ADA para inyectar el precio.
pyth_verificado = False
for staking_cred, amount in tx_info.withdrawals.items():
if isinstance(staking_cred, ScriptCredential):
if staking_cred.credential_hash == datum.pyth_script_id:
pyth_verificado = True

assert pyth_verificado, "No se encontro la prueba de Pyth en la transaccion"

# 3. Validar precio (el redeemer es el precio que enviamos desde el main)
assert redeemer >= datum.precio_minimo, "Precio insuficiente segun el oraculo"
82 changes: 82 additions & 0 deletions lazer/cardano/pyth-guard/contracts-python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import requests
import os
import json
import sys

# --- CONFIGURACIÓN ---
# ID de Pyth para ADA/USD (Pyth ID Network)
ADA_USD_FEED = "0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f"
# Hash del script de Pyth en Preview/PreProd Testnet
PYTH_SCRIPT_HASH = bytes.fromhex("f6a62c0b472f7783921b790d5656f7093259ba8790326847844a4286")

def obtener_datos_pyth():
print("[1/3] Consultando precio en Pyth Hermes...")
url = f"https://hermes.pyth.network/v2/updates/price/latest?ids[]={ADA_USD_FEED}"
try:
r = requests.get(url, timeout=10)
r.raise_for_status()
except requests.exceptions.RequestException as e:
print(f" ERROR al consultar Pyth: {e}")
sys.exit(1)

data = r.json()
parsed = data.get("parsed", [])
if not parsed:
print(" ERROR: No se encontraron datos de precio en la respuesta.")
sys.exit(1)

price_data = parsed[0]["price"]
precio_raw = int(price_data["price"])
exponente = int(price_data["expo"])
precio_real = precio_raw * (10 ** exponente)

# El 'VAA' es la prueba criptográfica que Cardano necesita
binary_data = data.get("binary", {}).get("data", [])
vaa_hex = binary_data[0] if binary_data else "N/A"

print(f" Precio Raw : {precio_raw}")
print(f" Exponente : {exponente}")
print(f" Precio Real: ${precio_real:.6f} USD")
return precio_raw, vaa_hex

def compilar_contrato():
print("[2/3] Compilando contrato Python a Plutus...")
contract_path = os.path.join(os.path.dirname(__file__), "contract.py")
exit_code = os.system(f"opshin build {contract_path}")
if exit_code != 0:
print(" ERROR: Falló la compilación del contrato con OpShin.")
print(" Asegurate de tener opshin instalado: pip install opshin")
sys.exit(1)
print(" Contrato guardado en build/contract.py/script.plutus")

def simular_transaccion(precio_raw: int, vaa: str):
print("[3/3] Construyendo transacción de Cardano (simulación)...")

# Precio mínimo: $0.40 USD en precio raw de Pyth (8 decimales → 40000000)
PRECIO_MINIMO = 40_000_000

print(f" Precio Oracle (Redeemer) : {precio_raw}")
print(f" Precio Mínimo (Datum) : {PRECIO_MINIMO}")

if precio_raw >= PRECIO_MINIMO:
print(" ✅ VALIDACIÓN OK: El precio supera el mínimo.")
else:
print(" ⚠️ VALIDACIÓN FALLA: Precio insuficiente. Stop-Loss se activaría.")

print(f" VAA de Pyth (proof) : {vaa[:40]}...")
print(" ¡Transacción lista para firmar con PyCardano!")

def main():
try:
compilar_contrato()
precio, vaa = obtener_datos_pyth()
simular_transaccion(precio, vaa)
print("\n--- PROCESO COMPLETADO EXITOSAMENTE ---")
except SystemExit:
pass
except Exception as e:
print(f"\nERROR INESPERADO: {e}")
raise

if __name__ == "__main__":
main()
11 changes: 11 additions & 0 deletions lazer/cardano/pyth-guard/contracts-python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PythGuard — Python Smart Contract Dependencies
# Instalar con: pip install -r requirements.txt

# OpShin: compilador Python → Plutus (Cardano)
opshin>=0.24.0

# PyCardano: librería Python para construir transacciones Cardano
pycardano>=0.10.0

# HTTP requests para consultar Pyth Hermes API
requests>=2.31.0
117 changes: 117 additions & 0 deletions lazer/cardano/pyth-guard/contracts/pyth_guard.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// PythGuard — Smart Contract en Aiken
// Sistema de Stop-Loss de Alta Precisión para Cardano + Pyth Network
// Hackathon Pythathon 2026

use aiken/hash.{Blake2b_224, Hash}
use aiken/list
use aiken/transaction.{ScriptContext, Spend, Transaction}
use aiken/transaction/credential.{VerificationKey}

// ============================================================
// TIPOS DE DATOS
// ============================================================

/// Datum: almacena el precio de activación del stop-loss y el dueño de los fondos.
pub type Datum {
/// Precio objetivo en micro-ADA (e.g. 350000 = $0.35 ADA/USD con 6 decimales)
target_price: Int,
/// Hash de la clave de verificación del propietario
owner: Hash<Blake2b_224, VerificationKey>,
}

/// Redeemer: actualización de precio firmada por el oráculo Pyth
pub type Redeemer {
/// Precio actual en micro-USD provisto por Pyth Lazer (6 decimales)
price: Int,
/// Firma del oráculo Pyth (debe coincidir con el PYTH_PREPROD_POLICY_ID)
pyth_signature: ByteArray,
/// Timestamp Unix del precio (para evitar price replay attacks)
publish_time: Int,
}

// ============================================================
// CONSTANTES
// ============================================================

/// Policy ID del oráculo Pyth en Cardano PreProd
/// Corresponde a: d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6
const pyth_preprod_policy_id: ByteArray =
#"d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6"

/// Ventana de validez de un precio Pyth: 5 segundos en Unix time
const max_price_age_seconds: Int = 5

// ============================================================
// VALIDADOR PRINCIPAL
// ============================================================

/// Validador de stop-loss de PythGuard.
///
/// Permite la liquidación/protección SOLO cuando:
/// 1. El precio del oráculo Pyth es <= al precio objetivo (stop-loss activado)
/// 2. La firma del oráculo coincide con el policy ID de Pyth en PreProd
/// 3. La transacción está firmada por el propietario de los fondos
/// 4. El precio no es demasiado antiguo (anti-replay)
validator {
fn validate(datum: Datum, redeemer: Redeemer, ctx: ScriptContext) -> Bool {
let ScriptContext { transaction, purpose } = ctx

when purpose is {
Spend(_) -> {
// Verificar que el precio del oráculo esté por debajo del umbral
let stop_loss_triggered = redeemer.price <= datum.target_price

// Verificar que la firma del oráculo sea legítima (Pyth PreProd)
let oracle_is_valid =
redeemer.pyth_signature == pyth_preprod_policy_id

// Verificar que el propietario está autorizando esta transacción
let owner_signed =
list.any(
transaction.extra_signatories,
fn(signer) { signer == datum.owner },
)

// Verificar que el precio no sea demasiado antiguo
// (comparación vs POSIXTime de la transacción)
let price_is_fresh =
check_price_freshness(redeemer.publish_time, transaction)

// Todas las condiciones deben cumplirse
stop_loss_triggered && oracle_is_valid && owner_signed && price_is_fresh
}
// Rechazar cualquier otro propósito de uso del script
_ -> False
}
}
}

// ============================================================
// FUNCIONES AUXILIARES
// ============================================================

/// Verifica que el precio del oráculo no sea demasiado antiguo.
/// El tiempo de publicación debe estar dentro de la ventana de validez
/// del intervalo de validez de la transacción.
fn check_price_freshness(
publish_time: Int,
transaction: Transaction,
) -> Bool {
let lower_bound_ms = get_lower_bound(transaction)
// Convertir max_price_age a milisegundos
let max_age_ms = max_price_age_seconds * 1000
// publish_time en segundos → convertir a ms
let publish_time_ms = publish_time * 1000
// El precio debe haber sido publicado dentro de la ventana
lower_bound_ms - publish_time_ms <= max_age_ms
}

/// Obtiene el límite inferior del intervalo de validez de la transacción en ms.
fn get_lower_bound(transaction: Transaction) -> Int {
let validity_range = transaction.validity_range
when validity_range.lower_bound.bound_type is {
aiken/interval.Finite(t) -> t
// Si no hay límite inferior definido, usar 0 (sin restricción de frescura)
_ -> 0
}
}
23 changes: 23 additions & 0 deletions lazer/cardano/pyth-guard/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/pyth-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="PythGuard — Sistema de Stop-Loss de alta precisión para Cardano usando Pyth Network. Feeds de sub-segundo a 400ms."
/>
<title>PythGuard | Stop-Loss Dashboard — Pyth × Cardano</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&family=Manrope:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading