diff --git a/lazer/cardano/pyth-guard/.env.example b/lazer/cardano/pyth-guard/.env.example new file mode 100644 index 00000000..4c7c580a --- /dev/null +++ b/lazer/cardano/pyth-guard/.env.example @@ -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 diff --git a/lazer/cardano/pyth-guard/.gitignore b/lazer/cardano/pyth-guard/.gitignore new file mode 100644 index 00000000..bb6c4a0b --- /dev/null +++ b/lazer/cardano/pyth-guard/.gitignore @@ -0,0 +1,8 @@ +.env +node_modules/ +dist/ +.DS_Store +*.js.map +venv/ +.venv/ +build/ diff --git a/lazer/cardano/pyth-guard/contracts-python/README.md b/lazer/cardano/pyth-guard/contracts-python/README.md new file mode 100644 index 00000000..b0863ee1 --- /dev/null +++ b/lazer/cardano/pyth-guard/contracts-python/README.md @@ -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 | diff --git a/lazer/cardano/pyth-guard/contracts-python/contract.py b/lazer/cardano/pyth-guard/contracts-python/contract.py new file mode 100644 index 00000000..8df23868 --- /dev/null +++ b/lazer/cardano/pyth-guard/contracts-python/contract.py @@ -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" diff --git a/lazer/cardano/pyth-guard/contracts-python/main.py b/lazer/cardano/pyth-guard/contracts-python/main.py new file mode 100644 index 00000000..fe506b67 --- /dev/null +++ b/lazer/cardano/pyth-guard/contracts-python/main.py @@ -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() diff --git a/lazer/cardano/pyth-guard/contracts-python/requirements.txt b/lazer/cardano/pyth-guard/contracts-python/requirements.txt new file mode 100644 index 00000000..40219aea --- /dev/null +++ b/lazer/cardano/pyth-guard/contracts-python/requirements.txt @@ -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 diff --git a/lazer/cardano/pyth-guard/contracts/pyth_guard.ak b/lazer/cardano/pyth-guard/contracts/pyth_guard.ak new file mode 100644 index 00000000..cd422d01 --- /dev/null +++ b/lazer/cardano/pyth-guard/contracts/pyth_guard.ak @@ -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, +} + +/// 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 + } +} diff --git a/lazer/cardano/pyth-guard/dashboard/index.html b/lazer/cardano/pyth-guard/dashboard/index.html new file mode 100644 index 00000000..286d79a6 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/index.html @@ -0,0 +1,23 @@ + + + + + + + + PythGuard | Stop-Loss Dashboard — Pyth × Cardano + + + + + +
+ + + diff --git a/lazer/cardano/pyth-guard/dashboard/package-lock.json b/lazer/cardano/pyth-guard/dashboard/package-lock.json new file mode 100644 index 00000000..add372b3 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/package-lock.json @@ -0,0 +1,2316 @@ +{ + "name": "pythguard-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pythguard-dashboard", + "version": "1.0.0", + "dependencies": { + "@supabase/supabase-js": "^2.99.3", + "framer-motion": "^11.2.13", + "lightweight-charts": "^4.2.3", + "lucide-react": "^0.577.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^2.12.7" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.4.5", + "vite": "^5.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@supabase/auth-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz", + "integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz", + "integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz", + "integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz", + "integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz", + "integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.99.3", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz", + "integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.99.3", + "@supabase/functions-js": "2.99.3", + "@supabase/postgrest-js": "2.99.3", + "@supabase/realtime-js": "2.99.3", + "@supabase/storage-js": "2.99.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fancy-canvas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz", + "integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightweight-charts": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.2.3.tgz", + "integrity": "sha512-5kS/2hY3wNYNzhnS8Gb+GAS07DX8GPF2YVDnd2NMC85gJVQ6RLU6YrXNgNJ6eg0AnWPwCnvaGtYmGky3HiLQEw==", + "license": "Apache-2.0", + "dependencies": { + "fancy-canvas": "2.1.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.577.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", + "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/lazer/cardano/pyth-guard/dashboard/package.json b/lazer/cardano/pyth-guard/dashboard/package.json new file mode 100644 index 00000000..a303a811 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/package.json @@ -0,0 +1,28 @@ +{ + "name": "pythguard-dashboard", + "private": true, + "version": "1.0.0", + "description": "PythGuard High-Frequency Stop-Loss Dashboard — Pyth Network × Cardano", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@supabase/supabase-js": "^2.99.3", + "framer-motion": "^11.2.13", + "lightweight-charts": "^4.2.3", + "lucide-react": "^0.577.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^2.12.7" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.4.5", + "vite": "^5.3.1" + } +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/App.tsx b/lazer/cardano/pyth-guard/dashboard/src/App.tsx new file mode 100644 index 00000000..81b28244 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/App.tsx @@ -0,0 +1,125 @@ +import { useState, useEffect, useRef, useCallback } from "react"; +import Header from "./components/Header"; +import AnalysisSidebar from "./components/AnalysisSidebar"; +import TradingChart from "./components/TradingChart"; +import OrderPanel from "./components/OrderPanel"; + +// ============================================================ +// TIPOS +// ============================================================ + +export interface PriceDataPoint { + time: string; + price: number; + timestamp: number; +} + +// ============================================================ +// CONSTANTES +// ============================================================ + +const STOP_LOSS_THRESHOLD = 0.35; +const UPDATE_INTERVAL_MS = 400; +const HISTORY_WINDOW = 80; +const DEX_DELAY_POINTS = 6; // puntos de delay para el oráculo estándar + +// ============================================================ +// SIMULACIÓN DE PRECIO ADA/USD +// ============================================================ + +function generateMockPrice(prev: number): number { + const t = Date.now() / 10000; + const sine = Math.sin(t * 0.7) * 0.045; + const noise = (Math.random() - 0.5) * 0.004; + const spike = Math.random() < 0.015 ? -(STOP_LOSS_THRESHOLD * 0.12) : 0; + return parseFloat(Math.max(0.28, Math.min(0.48, prev + sine * 0.03 + noise + spike)).toFixed(6)); +} + +// ============================================================ +// APP — TRADING TERMINAL +// ============================================================ + +export default function App() { + const [priceHistory, setPriceHistory] = useState([]); + const [currentPrice, setCurrentPrice] = useState(0.382); + const [isTriggered, setIsTriggered] = useState(false); + const [isLive, setIsLive] = useState(true); + const [savedSlippage, setSavedSlippage] = useState(0); + const [savedSlippageUsd, setSavedSlippageUsd] = useState(0); + const intervalRef = useRef | null>(null); + const priceRef = useRef(0.382); + + const tick = useCallback(() => { + const newPrice = generateMockPrice(priceRef.current); + priceRef.current = newPrice; + + const now = Date.now(); + const point: PriceDataPoint = { + time: new Date(now).toLocaleTimeString("es-AR", { hour12: false }), + price: newPrice, + timestamp: now, + }; + + setPriceHistory(prev => { + const next = [...prev, point].slice(-HISTORY_WINDOW); + // Calcular slippage acumulado vs oráculo retrasado + if (next.length > DEX_DELAY_POINTS) { + const oraclePrice = next[next.length - 1 - DEX_DELAY_POINTS].price; + const diff = Math.abs(newPrice - oraclePrice); + const pct = oraclePrice > 0 ? (diff / oraclePrice) * 100 : 0; + setSavedSlippage(prev => prev + pct * 0.1); // acumulado de sesión + setSavedSlippageUsd(prev => prev + diff * 100); // ADA * diferencia + } + return next; + }); + + setCurrentPrice(newPrice); + setIsTriggered(newPrice <= STOP_LOSS_THRESHOLD); + setIsLive(true); + }, []); + + useEffect(() => { + // Datos iniciales + const initial: PriceDataPoint[] = Array.from({ length: 40 }, (_, i) => { + const t = Date.now() - (40 - i) * UPDATE_INTERVAL_MS; + return { + time: new Date(t).toLocaleTimeString("es-AR", { hour12: false }), + price: 0.382 + (Math.random() - 0.5) * 0.03, + timestamp: t, + }; + }); + setPriceHistory(initial); + priceRef.current = initial[initial.length - 1].price; + + intervalRef.current = setInterval(tick, UPDATE_INTERVAL_MS); + return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; + }, [tick]); + + return ( +
+ {/* ── Header completo ── */} +
+ + {/* ── Cuerpo principal ── */} +
+ {/* Sidebar de análisis */} + + + {/* Gráfico de trading central */} + + + {/* Panel de operaciones */} + +
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/AnalysisSidebar.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/AnalysisSidebar.tsx new file mode 100644 index 00000000..497b7cce --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/AnalysisSidebar.tsx @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { + TrendingUp, + GitCommitHorizontal, + Ruler, + Pencil, + BarChart2, + Minus, + Crosshair, +} from "lucide-react"; + +const TOOLS = [ + { id: "trend", icon: TrendingUp, label: "Línea de Tendencia" }, + { id: "fibonacci", icon: GitCommitHorizontal, label: "Fibonacci" }, + { id: "ruler", icon: Ruler, label: "Regla" }, + { id: "brush", icon: Pencil, label: "Pincel" }, + { id: "horizontal", icon: Minus, label: "Línea Horizontal" }, + { id: "crosshair", icon: Crosshair, label: "Retícula" }, +]; + +const INDICATOR_TOOLS = [ + { id: "indicators", icon: BarChart2, label: "Indicadores" }, +]; + +export default function AnalysisSidebar() { + const [active, setActive] = useState(null); + + return ( + + ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/AuthModal.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/AuthModal.tsx new file mode 100644 index 00000000..9f3d1fe8 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/AuthModal.tsx @@ -0,0 +1,310 @@ +import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { X, User } from "lucide-react"; +import { supabase, Profile } from "../lib/supabase"; + +interface AuthModalProps { + isOpen: boolean; + onClose: () => void; + onProfileSaved: (profile: Profile) => void; + existingProfile: Profile | null; +} + +export default function AuthModal({ isOpen, onClose, onProfileSaved, existingProfile }: AuthModalProps) { + const [loading, setLoading] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); + const [fullName, setFullName] = useState(existingProfile?.full_name ?? ""); + const [session, setSession] = useState(null); + const [error, setError] = useState(null); + + // Check for session changes + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session); + }); + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setSession(session); + }); + return () => subscription.unsubscribe(); + }, []); + + // Pre-fill name from existing profile + useEffect(() => { + if (existingProfile?.full_name) { + setFullName(existingProfile.full_name); + } else if (session?.user?.user_metadata?.full_name) { + setFullName(session.user.user_metadata.full_name); + } + }, [existingProfile, session]); + + const handleGoogleLogin = async () => { + setLoading(true); + setError(null); + const { error } = await supabase.auth.signInWithOAuth({ + provider: "google", + options: { + redirectTo: window.location.origin, + }, + }); + if (error) setError(error.message); + setLoading(false); + }; + + const handleSaveProfile = async () => { + if (!session?.user) return; + setSaveLoading(true); + setError(null); + + const { data, error } = await supabase + .from("profiles") + .upsert({ + id: session.user.id, + email: session.user.email, + full_name: fullName.trim(), + avatar_url: session.user.user_metadata?.avatar_url ?? null, + updated_at: new Date().toISOString(), + }, { onConflict: "id" }) + .select() + .single(); + + if (error) { + setError(error.message); + } else if (data) { + onProfileSaved(data as Profile); + onClose(); + } + setSaveLoading(false); + }; + + const handleSignOut = async () => { + await supabase.auth.signOut(); + setSession(null); + onClose(); + }; + + if (!isOpen) return null; + + return ( + + + e.stopPropagation()} + > + {/* Close button */} + + + {/* Header */} +
+ {session?.user ? ( + <> + {session.user.user_metadata?.avatar_url ? ( + Profile + ) : ( +
+ +
+ )} +

+ Hola, {session.user.user_metadata?.name?.split(" ")[0] ?? "Operador"} 👋 +

+

{session.user.email}

+ + ) : ( + <> +
+ +
+

+ Ingresar a PythGuard +

+

+ Accedé con tu cuenta de Google para guardar tu perfil. +

+ + )} +
+ + {/* Content */} + {session?.user ? ( +
+
+ + setFullName(e.target.value)} + placeholder="Ej: Ana García" + style={{ + width: "100%", + padding: "0.875rem 1rem", + borderRadius: "12px", + border: "1.5px solid var(--surface-container)", + background: "var(--surface-container-low)", + color: "var(--on-background)", + fontFamily: "var(--font-body)", + fontSize: "1rem", + outline: "none", + transition: "border-color 200ms", + }} + onFocus={(e) => e.target.style.borderColor = "var(--primary)"} + onBlur={(e) => e.target.style.borderColor = "var(--surface-container)"} + /> +
+ + {error && ( +

+ {error} +

+ )} + + + + +
+ ) : ( +
+ {error && ( +

+ {error} +

+ )} + +
+ )} +
+
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/Header.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/Header.tsx new file mode 100644 index 00000000..f0c3a650 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/Header.tsx @@ -0,0 +1,286 @@ +import { useState, useEffect } from "react"; +import { Bell, ChevronDown, Shield, Wallet, WifiOff, Sun, Moon, ExternalLink, Loader2, AlertCircle } from "lucide-react"; +import { supabase, Profile } from "../lib/supabase"; +import AuthModal from "./AuthModal"; + +interface HeaderProps { + currentPrice: number; + isLive: boolean; +} + +const ASSETS = ["ADA/USD", "BTC/USD", "ETH/USD", "SOL/USD"]; + +// Convierte hex CBOR address a visualización bech32 abreviada +// CIP-30 retorna las directions en hex. Para mostrar buscamos el prefijo estándar +// o simplemente abreviamos: "addr1q..." (primeros/últimos chars) +function formatAddr(hexAddr: string): string { + if (!hexAddr) return ""; + // Heurística: si empieza con "01" es una address de pago Shelley (mainnet) + // Lo mostramos como addr1...XXXX sin conversión pesada + const short = hexAddr.slice(0, 8).toLowerCase(); + const tail = hexAddr.slice(-6).toLowerCase(); + return `addr1…${short}…${tail}`; +} + +// Convierte Lovelace (hex CBOR) a ADA (número) +function lovelaceToAda(hexBalance: string): string { + try { + // CIP-30 devuelve el balance como número hex simple en muchos wallets + const lovelace = parseInt(hexBalance, 16); + if (isNaN(lovelace)) return "0.00"; + return (lovelace / 1_000_000).toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } catch { + return "0.00"; + } +} + +type WalletState = "disconnected" | "connecting" | "connected" | "no_lace" | "error"; + +export default function Header({ currentPrice, isLive }: HeaderProps) { + const [selectedAsset, setSelectedAsset] = useState("ADA/USD"); + const [assetOpen, setAssetOpen] = useState(false); + const [authOpen, setAuthOpen] = useState(false); + const [profile, setProfile] = useState(null); + const [isLight, setIsLight] = useState(false); + + // Wallet state + const [walletState, setWalletState] = useState("disconnected"); + const [walletAddr, setWalletAddr] = useState(""); + const [walletBalance, setWalletBalance] = useState(""); + const [walletError, setWalletError] = useState(""); + + const toggleTheme = () => { + const next = !isLight; + setIsLight(next); + document.documentElement.setAttribute("data-theme", next ? "light" : "dark"); + }; + + // Supabase auth + useEffect(() => { + supabase.auth.getSession().then(async ({ data: { session } }) => { + if (session?.user) { + const { data } = await supabase.from("profiles").select("*").eq("id", session.user.id).maybeSingle(); + if (data) setProfile(data as Profile); + } + }); + supabase.auth.onAuthStateChange(async (_ev, session) => { + if (session?.user) { + const { data } = await supabase.from("profiles").select("*").eq("id", session.user.id).maybeSingle(); + setProfile(data as Profile ?? null); + } else setProfile(null); + }); + }, []); + + // ── Lace Wallet CIP-30 ────────────────────────────────────────────────────── + const connectLace = async () => { + const lace = window.cardano?.lace; + + if (!lace) { + setWalletState("no_lace"); + return; + } + + try { + setWalletState("connecting"); + setWalletError(""); + + // Solicitar habilitación al usuario (abre popup de Lace) + const api = await lace.enable(); + + // Obtener dirección de cambio (primera dirección usable) + const changeAddrHex = await api.getChangeAddress(); + setWalletAddr(formatAddr(changeAddrHex)); + + // Obtener balance en lovelace + const balanceHex = await api.getBalance(); + setWalletBalance(lovelaceToAda(balanceHex)); + + setWalletState("connected"); + } catch (err: any) { + // El usuario rechazó la conexión o hubo un error + const msg = err?.message ?? "Error al conectar"; + if (msg.includes("cancel") || msg.includes("user declined") || msg.includes("rejected")) { + setWalletState("disconnected"); + } else { + setWalletError(msg); + setWalletState("error"); + } + } + }; + + const disconnectWallet = () => { + setWalletState("disconnected"); + setWalletAddr(""); + setWalletBalance(""); + setWalletError(""); + }; + + // ── UI helpers ────────────────────────────────────────────────────────────── + + const WalletWidget = () => { + if (walletState === "connected") { + return ( +
+
+ +
+ + {walletAddr} + + {walletBalance && ( + + {walletBalance} ADA + + )} +
+
+
+ ); + } + + if (walletState === "connecting") { + return ( + + ); + } + + if (walletState === "no_lace") { + return ( + + + Instalar Lace + + ); + } + + if (walletState === "error") { + return ( + + ); + } + + // disconnected (default) + return ( + + ); + }; + + return ( + <> + setAuthOpen(false)} onProfileSaved={setProfile} existingProfile={profile} /> +
+ {/* Izquierda: Logo + Estado de Red */} + + + PythGuard + +
+ + {isLive ? ( + Red en Vivo + ) : ( + + Sin Conexión + + )} +
+ +
+ + {/* Selector de Activo */} +
+ + {assetOpen && ( +
+ {ASSETS.map(a => ( + + ))} +
+ )} +
+ + {/* Precio actual inline */} + + ${currentPrice.toFixed(6)} + + + {/* Spacer */} +
+ + {/* Wallet + Controles */} +
+ + +
+ + {/* Tema claro/oscuro */} + + + {/* Notificaciones */} + + + {/* Perfil */} + +
+
+ + {/* Spinner keyframe (inyectado inline para no contaminar index.css) */} + + + ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/OrderPanel.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/OrderPanel.tsx new file mode 100644 index 00000000..c2b8f12b --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/OrderPanel.tsx @@ -0,0 +1,177 @@ +import { useState, useRef } from "react"; +import { AlertTriangle, Zap } from "lucide-react"; + +interface OrderPanelProps { + currentPrice: number; + isTriggered: boolean; + savedSlippage: number; + savedSlippageUsd: number; +} + +export default function OrderPanel({ currentPrice, isTriggered, savedSlippage, savedSlippageUsd }: OrderPanelProps) { + const [tab, setTab] = useState<"market" | "limit">("market"); + const [stopLossActive, setStopLossActive] = useState(false); + const [execPrice, setExecPrice] = useState(""); + const [quantity, setQuantity] = useState(""); + const [stopLossPrice, setStopLossPrice] = useState("0.350000"); + const [panicConfirm, setPanicConfirm] = useState(false); + const panicTimeout = useRef | null>(null); + + const displayPrice = tab === "market" ? currentPrice : parseFloat(execPrice) || currentPrice; + const qty = parseFloat(quantity) || 0; + const totalUsd = (qty * displayPrice).toFixed(2); + + const handlePanic = () => { + if (!panicConfirm) { + setPanicConfirm(true); + panicTimeout.current = setTimeout(() => setPanicConfirm(false), 3000); + } else { + if (panicTimeout.current) clearTimeout(panicTimeout.current); + setPanicConfirm(false); + // Aquí iría la lógica real de cierre de posiciones + alert("Saliendo de todas las posiciones..."); + } + }; + + return ( + + ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/PriceChart.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/PriceChart.tsx new file mode 100644 index 00000000..3e0abf51 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/PriceChart.tsx @@ -0,0 +1,106 @@ +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + ReferenceLine, +} from "recharts"; +import { PriceDataPoint } from "../App"; + +interface PriceChartProps { + data: PriceDataPoint[]; + threshold: number; + isTriggered: boolean; +} + +export default function PriceChart({ data, threshold, isTriggered }: PriceChartProps) { + const chartColor = isTriggered ? "var(--error)" : "var(--primary)"; + const areaColor = isTriggered ? "var(--error-container)" : "var(--primary-container)"; + + return ( +
+
+

+ ADA Evolution — 400ms interval +

+
+ + + Market Price + + + + Target Protection + +
+
+ +
+ + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/SafetyIndicator.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/SafetyIndicator.tsx new file mode 100644 index 00000000..522e7b75 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/SafetyIndicator.tsx @@ -0,0 +1,121 @@ +import { motion, AnimatePresence } from "framer-motion"; + +interface SafetyIndicatorProps { + isStopLossTriggered: boolean; + currentPrice: number; + threshold: number; +} + +export default function SafetyIndicator({ + isStopLossTriggered, + currentPrice, + threshold, +}: SafetyIndicatorProps) { + const statusLabel = isStopLossTriggered ? "Target Reached" : "Safe Zone"; + const statusDesc = isStopLossTriggered + ? "Protective action taken to secure your assets." + : "Your portfolio is currently within the safe market range."; + + return ( +
+
+

Security Status

+
+ + + {statusLabel} + +
+
+ +
+ + {!isStopLossTriggered ? ( + + 🛡️ + + + ) : ( + + ⚠️ + + + )} + +
+ +
+

+ {statusLabel} +

+

+ {statusDesc} +

+
+ +
+
+ Current Deviation + + {((currentPrice / threshold - 1) * 100).toFixed(2)}% + +
+
+ +
+
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/SlippagePanel.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/SlippagePanel.tsx new file mode 100644 index 00000000..5926a0fc --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/SlippagePanel.tsx @@ -0,0 +1,82 @@ +import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +interface SlippagePanelProps { + pythPrice: number; + dexPriceRef: React.MutableRefObject; + isTriggered: boolean; +} + +export default function SlippagePanel({ pythPrice, dexPriceRef, isTriggered }: SlippagePanelProps) { + const [sessionSavings, setSessionSavings] = useState(0); + + const dexPrice = dexPriceRef.current; + const slippage = Math.abs(pythPrice - dexPrice); + const slippagePct = (slippage / pythPrice) * 100; + + useEffect(() => { + if (isTriggered && slippage > 0) { + setSessionSavings((prev) => prev + slippage * 100); // Simula impacto en 100 ADA + } + }, [isTriggered, slippage]); + + return ( +
+
+

Market Intelligence

+

Real-time comparison between Pyth Lazer and on-chain DEX providers.

+
+ +
+ {/* Comparison Card */} +
+
+ Pyth Lazer (HQ) + ${pythPrice.toFixed(6)} +
+
+ Standard DEX + ${dexPrice.toFixed(6)} +
+
+ + {/* Savings Insight */} +
+
+

+ Slippage Prevention +

+
+ + {slippagePct.toFixed(3)}% + + Optimized +
+
+ {/* Subtle background glow */} +
+
+
+ +
+

Total Protection Impact

+ + + ${sessionSavings.toFixed(2)} Saved + + +
+ +
+

+ "Using sub-second feeds ensures you exit before flash crash slippage occurs." +

+
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/components/TradingChart.tsx b/lazer/cardano/pyth-guard/dashboard/src/components/TradingChart.tsx new file mode 100644 index 00000000..fbd6d7d1 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/components/TradingChart.tsx @@ -0,0 +1,349 @@ +import { useRef, useEffect, useState } from "react"; +import { + createChart, + IChartApi, + ISeriesApi, + ColorType, + CrosshairMode, + LineStyle, + UTCTimestamp, +} from "lightweight-charts"; +import { PriceDataPoint } from "../App"; + +interface TradingChartProps { + data: PriceDataPoint[]; + threshold: number; + isTriggered: boolean; +} + +const ORACLE_DELAY = 6; // ticks (6 × 400ms = 2.4s de desventaja del oráculo estándar) + +const TIMEFRAMES = [ + { label: "1m", ticks: 150 }, + { label: "5m", ticks: 750 }, + { label: "15m", ticks: 2250 }, + { label: "30m", ticks: 4500 }, + { label: "1H", ticks: 9000 }, + { label: "4H", ticks: 36000 }, + { label: "1D", ticks: 216000}, +] as const; + +function getTheme() { + const isLight = document.documentElement.getAttribute("data-theme") === "light"; + return { + bg: isLight ? "#faf8ff" : "#0d0f1a", + text: isLight ? "#5e5a7a" : "#6b6b8a", + grid: isLight ? "rgba(149,130,220,0.18)" : "rgba(42,45,69,0.7)", + border: isLight ? "#d5cff0" : "#2a2d45", + pyth: isLight ? "#6549c0" : "#7c5cfa", + oracle: isLight ? "rgba(101,73,192,0.38)" : "rgba(180,160,255,0.38)", + cross: isLight ? "rgba(101,73,192,0.55)" : "rgba(124,92,250,0.55)", + buyVol: isLight ? "rgba(0,168,84,0.65)" : "rgba(0,210,106,0.65)", + sellVol: isLight ? "rgba(224,51,81,0.65)" : "rgba(255,61,90,0.65)", + }; +} + +function toTime(index: number): UTCTimestamp { + // Use sequential index as time axis to avoid duplicate timestamps + // (400ms interval → multiple points per second → crash if using wall-clock seconds) + return index as UTCTimestamp; +} + +export default function TradingChart({ data, threshold, isTriggered }: TradingChartProps) { + const [activeTF, setActiveTF] = useState("5m"); + + const zoomToTF = (label: string, ticks: number) => { + setActiveTF(label); + const chart = priceChartRef.current; + if (!chart || data.length === 0) return; + const to = data.length - 1; + const from = Math.max(0, to - ticks); + chart.timeScale().setVisibleRange({ from: from as UTCTimestamp, to: to as UTCTimestamp }); + }; + const priceRef = useRef(null); + const volumeRef = useRef(null); + + const priceChartRef = useRef(null); + const volumeChartRef = useRef(null); + const pythRef = useRef | null>(null); + const oracleRef = useRef | null>(null); + const volRef = useRef | null>(null); + const lastIdxRef = useRef(-1); + + useEffect(() => { + if (!priceRef.current || !volumeRef.current) return; + const t = getTheme(); + + const sharedOpts = (h: number, withTime: boolean) => ({ + layout: { + background: { type: ColorType.Solid, color: t.bg }, + textColor: t.text, + fontFamily: "'JetBrains Mono', monospace", + fontSize: 11, + }, + grid: { + vertLines: { color: t.grid, style: LineStyle.Dashed }, + horzLines: { color: t.grid, style: LineStyle.Dashed }, + }, + rightPriceScale: { borderColor: t.border }, + timeScale: { + borderColor: t.border, + timeVisible: withTime, + secondsVisible: withTime, + rightOffset: 8, + barSpacing: 6, + }, + height: h, + }); + + // Price chart + const pc = createChart(priceRef.current, { + ...sharedOpts(priceRef.current.clientHeight || 340, true), + width: priceRef.current.clientWidth, + crosshair: { + mode: CrosshairMode.Normal, + vertLine: { color: t.cross, width: 1, style: LineStyle.Dashed, labelBackgroundColor: t.pyth }, + horzLine: { color: t.cross, width: 1, style: LineStyle.Dashed, labelBackgroundColor: t.pyth }, + }, + }); + priceChartRef.current = pc; + + // Oracle line — tenue, discontinued + const oracleSeries = pc.addLineSeries({ + color: t.oracle, + lineWidth: 1, + lineStyle: LineStyle.Dashed, + lastValueVisible: false, + priceLineVisible: false, + crosshairMarkerVisible: false, + }); + oracleRef.current = oracleSeries; + + // Pyth price line — sólido, primario + const pythSeries = pc.addLineSeries({ + color: t.pyth, + lineWidth: 2, + lastValueVisible: true, + priceLineVisible: false, + crosshairMarkerRadius: 4, + }); + pythRef.current = pythSeries; + + // Stop-loss price line + pythSeries.createPriceLine({ + price: threshold, + color: "rgba(255,61,90,0.75)", + lineWidth: 1, + lineStyle: LineStyle.Dashed, + axisLabelVisible: true, + title: "Stop-Loss", + }); + + // Volume chart + const vc = createChart(volumeRef.current, { + ...sharedOpts(volumeRef.current.clientHeight || 160, false), + width: volumeRef.current.clientWidth, + crosshair: { mode: CrosshairMode.Hidden }, + rightPriceScale: { visible: false }, + leftPriceScale: { visible: false }, + timeScale: { visible: false }, + }); + volumeChartRef.current = vc; + + const volSeries = vc.addHistogramSeries({ + color: t.buyVol, + priceFormat: { type: "volume" }, + priceScaleId: "", + }); + vc.priceScale("").applyOptions({ scaleMargins: { top: 0.05, bottom: 0 } }); + volRef.current = volSeries; + + // Sync time scales between charts + pc.timeScale().subscribeVisibleLogicalRangeChange(range => { + if (range) vc.timeScale().setVisibleLogicalRange(range); + }); + vc.timeScale().subscribeVisibleLogicalRangeChange(range => { + if (range) pc.timeScale().setVisibleLogicalRange(range); + }); + + // Responsive resize + const ro = new ResizeObserver(() => { + if (priceRef.current) pc.applyOptions({ width: priceRef.current.clientWidth }); + if (volumeRef.current) vc.applyOptions({ width: volumeRef.current.clientWidth }); + }); + ro.observe(priceRef.current); + ro.observe(volumeRef.current); + + // Theme sync (dark ↔ light) + const mo = new MutationObserver(() => { + const nt = getTheme(); + [pc, vc].forEach(chart => chart.applyOptions({ + layout: { background: { type: ColorType.Solid, color: nt.bg }, textColor: nt.text }, + grid: { vertLines: { color: nt.grid }, horzLines: { color: nt.grid } }, + })); + pythSeries.applyOptions({ color: nt.pyth }); + oracleSeries.applyOptions({ color: nt.oracle }); + }); + mo.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] }); + + return () => { + ro.disconnect(); + mo.disconnect(); + pc.remove(); + vc.remove(); + priceChartRef.current = volumeChartRef.current = null; + pythRef.current = oracleRef.current = volRef.current = null; + lastIdxRef.current = -1; + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // ── Incremental updates (O(1) canvas paint per tick) ── + useEffect(() => { + if (!pythRef.current || !oracleRef.current || !volRef.current || data.length === 0) return; + const t = getTheme(); + + if (lastIdxRef.current === -1) { + // Bulk initial load + pythRef.current.setData(data.map((d, i) => ({ time: toTime(i), value: d.price }))); + oracleRef.current.setData(data.map((d, i) => ({ + time: toTime(i), + value: data[Math.max(0, i - ORACLE_DELAY)].price, + }))); + volRef.current.setData(data.map((d, i) => { + const isBuy = d.price >= (data[Math.max(0, i - 1)]?.price ?? d.price); + return { time: toTime(i), value: 50000 + Math.abs(Math.sin(i * 0.7)) * 80000, color: isBuy ? t.buyVol : t.sellVol }; + })); + lastIdxRef.current = data.length - 1; + priceChartRef.current?.timeScale().scrollToRealTime(); + return; + } + + // Incremental update — only the new ticks + for (let i = lastIdxRef.current + 1; i < data.length; i++) { + const d = data[i]; + const time = toTime(i); + const isBuy = d.price >= (data[Math.max(0, i - 1)]?.price ?? d.price); + pythRef.current.update({ time, value: d.price }); + oracleRef.current.update({ time, value: data[Math.max(0, i - ORACLE_DELAY)].price }); + volRef.current.update({ time, value: 50000 + Math.abs(Math.sin(i * 0.7)) * 80000, color: isBuy ? t.buyVol : t.sellVol }); + } + lastIdxRef.current = data.length - 1; + }, [data]); + + const latestPrice = data[data.length - 1]?.price ?? 0; + const prevPrice = data[data.length - 2]?.price ?? latestPrice; + const delta = latestPrice - prevPrice; + const pct = prevPrice > 0 ? (delta / prevPrice) * 100 : 0; + const isUp = delta >= 0; + + // Pyth lead: cuántos ms de ventaja tiene Pyth sobre el oráculo estándar + const pythLeadMs = ORACLE_DELAY * 400; + const pythLeadPrice = data.length >= ORACLE_DELAY + 1 + ? latestPrice - data[data.length - 1 - ORACLE_DELAY].price + : 0; + + return ( +
+ + {/* ── Barra superior: precio + leyendas ── */} +
+
+ + ADA / USD + +
+ + ${latestPrice.toFixed(6)} + + + {isUp ? "▲" : "▼"} {pct.toFixed(4)}% + + {isTriggered && ( + + ⚠ STOP-LOSS ACTIVO + + )} +
+
+
+
+
+
+ Precio Pyth +
+
+
+ Oráculo (retardado) +
+ + {/* ⚡ Ventaja de velocidad Pyth */} +
+ + ⚡ {pythLeadMs}ms + adelanto vs oráculo + {pythLeadPrice !== 0 && ( + 0 ? "var(--buy)" : "var(--sell)", fontWeight: 600 }}> + ({pythLeadPrice > 0 ? "+" : ""}{pythLeadPrice.toFixed(4)}) + + )} +
+ + + Canvas · 60fps + +
+
+ + {/* ── Pills de temporalidad ── */} +
+ {TIMEFRAMES.map(({ label, ticks }) => ( + + ))} +
+ + {data.length} ticks · ~{(data.length * 0.4 / 60).toFixed(1)} min de datos + +
+ + {/* Price chart — 65% */} +
+ +
+ + {/* Volume histogram — 35% */} +
+
+ ); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/index.css b/lazer/cardano/pyth-guard/dashboard/src/index.css new file mode 100644 index 00000000..1f660fd5 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/index.css @@ -0,0 +1,576 @@ +/* ============================================================ + PYTHGUARD — Terminal de Trading + Dark Theme + Lavender Accent + ============================================================ */ + +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); + +/* ── Tokens ── */ +:root { + --font-editorial: 'Plus Jakarta Sans', sans-serif; + --font-body: 'Manrope', sans-serif; + --font-mono: 'JetBrains Mono', monospace; + + /* Dark terminal palette */ + --bg: #0d0f1a; + --surface: #141623; + --surface-container: #1a1d2e; + --surface-container-low: #16182a; + --surface-container-lowest: #101320; + --on-background: #e4e2f0; + --on-surface: #c5c2d8; + --outline: #2a2d45; + --outline-faint: #1f2138; + + /* Brand */ + --primary: #7c5cfa; + --primary-light: #9d82fc; + --primary-container: rgba(124, 92, 250, 0.15); + --secondary-container: rgba(124, 92, 250, 0.08); + + /* Semantic */ + --buy: #00d26a; + --buy-container: rgba(0, 210, 106, 0.12); + --sell: #ff3d5a; + --sell-container: rgba(255, 61, 90, 0.12); + --error: #ff3d5a; + --warn: #f5a623; + + /* Chart */ + --chart-pyth: #7c5cfa; + --chart-oracle: rgba(180, 160, 255, 0.35); + --chart-grid: rgba(42, 45, 69, 0.6); + --chart-bg: #0d0f1a; + + /* Misc */ + --header-h: 56px; + --sidebar-w: 52px; + --panel-w: 340px; + --radius: 10px; + --radius-sm: 6px; +} + +/* ── Reset ── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html, body, #root { + height: 100%; + overflow: hidden; + background: var(--bg); + color: var(--on-background); + font-family: var(--font-body); + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +#root { + display: flex; + flex-direction: column; +} + +/* ── Scrollbars ── */ +::-webkit-scrollbar { width: 4px; height: 4px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--outline); border-radius: 2px; } + +/* ── Typography ── */ +.title-lg { + font-family: var(--font-editorial); + font-weight: 800; + font-size: clamp(1.6rem, 2.5vw, 2.2rem); + letter-spacing: -0.03em; + line-height: 1.1; +} +.title-md { + font-family: var(--font-editorial); + font-weight: 700; + font-size: clamp(1.1rem, 1.5vw, 1.4rem); + letter-spacing: -0.02em; +} +.text-muted { color: var(--on-surface); } +.text-mono { font-family: var(--font-mono); } +.text-buy { color: var(--buy); } +.text-sell { color: var(--sell); } + +/* ── Header ── */ +.header { + height: var(--header-h); + min-height: var(--header-h); + background: var(--surface); + border-bottom: 1px solid var(--outline); + display: flex; + align-items: center; + padding: 0 1rem; + gap: 1rem; + z-index: 100; + flex-shrink: 0; +} + +.header-logo { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: var(--font-editorial); + font-weight: 800; + font-size: 1rem; + color: var(--on-background); + letter-spacing: -0.02em; + text-decoration: none; + white-space: nowrap; +} +.header-logo svg { color: var(--primary); } + +.status-dot { + width: 7px; height: 7px; + border-radius: 50%; + flex-shrink: 0; +} +.status-dot.live { + background: var(--buy); + box-shadow: 0 0 6px var(--buy); + animation: pulse-dot 2s ease-in-out infinite; +} +.status-dot.offline { background: var(--outline); } + +@keyframes pulse-dot { + 0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--buy); } + 50% { opacity: 0.6; box-shadow: 0 0 2px var(--buy); } +} + +.header-divider { + width: 1px; + height: 24px; + background: var(--outline); + flex-shrink: 0; +} + +/* Asset selector */ +.asset-select { + display: flex; + align-items: center; + gap: 0.4rem; + background: var(--surface-container); + border: 1px solid var(--outline); + border-radius: var(--radius-sm); + padding: 0.35rem 0.75rem; + cursor: pointer; + font-family: var(--font-editorial); + font-weight: 700; + font-size: 0.95rem; + color: var(--on-background); + transition: border-color 150ms; +} +.asset-select:hover { border-color: var(--primary); } + +/* Wallet button */ +.wallet-btn { + display: flex; + align-items: center; + gap: 0.5rem; + background: var(--primary-container); + border: 1px solid var(--primary); + border-radius: var(--radius-sm); + padding: 0.35rem 0.85rem; + cursor: pointer; + font-family: var(--font-editorial); + font-weight: 600; + font-size: 0.85rem; + color: var(--primary-light); + transition: all 150ms; +} +.wallet-btn:hover { background: rgba(124, 92, 250, 0.25); } + +/* Icon button (notifications, profile) */ +.icon-btn { + width: 34px; height: 34px; + border-radius: 50%; + border: 1px solid var(--outline); + background: var(--surface-container); + display: flex; align-items: center; justify-content: center; + cursor: pointer; + color: var(--on-surface); + transition: all 150ms; + position: relative; + flex-shrink: 0; +} +.icon-btn:hover { border-color: var(--primary); color: var(--primary-light); } + +.notification-dot { + position: absolute; + top: 4px; right: 4px; + width: 7px; height: 7px; + border-radius: 50%; + background: var(--sell); + border: 1.5px solid var(--surface); +} + +.profile-pic { + width: 32px; height: 32px; + border-radius: 50%; + object-fit: cover; + border: 1.5px solid var(--primary); + cursor: pointer; +} + +/* ── Analysis Sidebar ── */ +.analysis-sidebar { + width: var(--sidebar-w); + min-width: var(--sidebar-w); + background: var(--surface); + border-right: 1px solid var(--outline); + display: flex; + flex-direction: column; + align-items: center; + padding: 0.75rem 0; + gap: 0.25rem; + overflow-y: auto; +} + +.tool-btn { + width: 36px; height: 36px; + border-radius: var(--radius-sm); + border: none; + background: transparent; + display: flex; align-items: center; justify-content: center; + cursor: pointer; + color: var(--on-surface); + transition: all 150ms; + position: relative; +} +.tool-btn:hover, .tool-btn.active { + background: var(--primary-container); + color: var(--primary-light); +} +.tool-btn.active::before { + content: ''; + position: absolute; + left: 0; top: 50%; + transform: translateY(-50%); + width: 2px; height: 60%; + background: var(--primary); + border-radius: 0 2px 2px 0; +} +.tool-separator { + width: 28px; height: 1px; + background: var(--outline); + margin: 0.25rem 0; +} + +/* ── Chart Panel ── */ +.chart-panel { + flex: 1; + background: var(--chart-bg); + display: flex; + flex-direction: column; + overflow: hidden; + border-right: 1px solid var(--outline); +} + +.chart-header { + padding: 0.6rem 1rem; + border-bottom: 1px solid var(--outline); + display: flex; + align-items: center; + gap: 1rem; + background: var(--surface-container-lowest); + flex-shrink: 0; +} + +.price-tag { + font-family: var(--font-mono); + font-weight: 600; + font-size: 1.25rem; + color: var(--on-background); +} + +.chart-legend-item { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.75rem; + color: var(--on-surface); +} +.legend-line { + width: 16px; height: 2px; + border-radius: 1px; +} + +/* ── Order Panel ── */ +.order-panel { + width: var(--panel-w); + min-width: var(--panel-w); + background: var(--surface); + border-left: 1px solid var(--outline); + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.order-section { + padding: 1rem; + border-bottom: 1px solid var(--outline); +} +.order-section:last-child { border-bottom: none; } + +.section-title { + font-family: var(--font-editorial); + font-weight: 700; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--on-surface); + margin-bottom: 0.75rem; +} + +/* Tabs */ +.tab-group { + display: flex; + background: var(--surface-container); + border-radius: var(--radius-sm); + padding: 3px; + gap: 2px; + margin-bottom: 1rem; +} +.tab-btn { + flex: 1; + padding: 0.4rem; + border-radius: calc(var(--radius-sm) - 2px); + border: none; + background: transparent; + cursor: pointer; + font-family: var(--font-editorial); + font-weight: 600; + font-size: 0.8rem; + color: var(--on-surface); + transition: all 150ms; +} +.tab-btn.active { + background: var(--surface); + color: var(--on-background); + box-shadow: 0 1px 4px rgba(0,0,0,0.3); +} + +/* Number inputs */ +.input-group { margin-bottom: 0.75rem; } +.input-label { + display: block; + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--on-surface); + margin-bottom: 0.35rem; +} +.input-field { + width: 100%; + padding: 0.6rem 0.75rem; + background: var(--surface-container); + border: 1px solid var(--outline); + border-radius: var(--radius-sm); + color: var(--on-background); + font-family: var(--font-mono); + font-size: 0.9rem; + outline: none; + transition: border-color 150ms; +} +.input-field:focus { border-color: var(--primary); } +.input-field::placeholder { color: var(--on-surface); opacity: 0.5; } + +/* Action buttons */ +.btn-buy { + width: 100%; + padding: 0.7rem; + border-radius: var(--radius-sm); + border: none; + background: var(--buy); + color: #fff; + font-family: var(--font-editorial); + font-weight: 700; + font-size: 0.9rem; + cursor: pointer; + transition: all 150ms; + box-shadow: 0 0 16px rgba(0, 210, 106, 0.25); +} +.btn-buy:hover { filter: brightness(1.1); box-shadow: 0 0 24px rgba(0, 210, 106, 0.40); } + +.btn-sell { + width: 100%; + padding: 0.7rem; + border-radius: var(--radius-sm); + border: none; + background: var(--sell); + color: #fff; + font-family: var(--font-editorial); + font-weight: 700; + font-size: 0.9rem; + cursor: pointer; + transition: all 150ms; + box-shadow: 0 0 16px rgba(255, 61, 90, 0.25); +} +.btn-sell:hover { filter: brightness(1.1); box-shadow: 0 0 24px rgba(255, 61, 90, 0.40); } + +/* Neon switch (Stop-Loss) */ +.switch-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} +.switch-label { + font-family: var(--font-editorial); + font-weight: 600; + font-size: 0.85rem; + color: var(--on-background); +} +.switch-sub { + font-size: 0.72rem; + color: var(--on-surface); + margin-top: 0.15rem; +} + +.neon-switch { + position: relative; + width: 44px; height: 24px; + flex-shrink: 0; +} +.neon-switch input { opacity: 0; width: 0; height: 0; } +.neon-slider { + position: absolute; + inset: 0; + border-radius: 12px; + background: var(--surface-container); + border: 1px solid var(--outline); + cursor: pointer; + transition: all 250ms; +} +.neon-slider::before { + content: ''; + position: absolute; + width: 16px; height: 16px; + border-radius: 50%; + background: var(--on-surface); + top: 3px; left: 3px; + transition: all 250ms; +} +.neon-switch input:checked + .neon-slider { + background: var(--primary-container); + border-color: var(--primary); + box-shadow: 0 0 12px rgba(124, 92, 250, 0.5); +} +.neon-switch input:checked + .neon-slider::before { + transform: translateX(20px); + background: var(--primary); + box-shadow: 0 0 8px var(--primary); +} + +/* Performance widget */ +.performance-widget { + background: var(--surface-container-low); + border: 1px solid var(--outline); + border-radius: var(--radius-sm); + padding: 0.75rem; + margin-bottom: 0.75rem; +} +.perf-label { + font-size: 0.72rem; + color: var(--on-surface); + text-transform: uppercase; + letter-spacing: 0.06em; +} +.perf-value { + font-family: var(--font-mono); + font-weight: 600; + font-size: 1.1rem; + color: var(--buy); + margin-top: 0.2rem; +} + +/* Panic button */ +.btn-panic { + width: 100%; + padding: 0.75rem; + border-radius: var(--radius-sm); + border: 1.5px solid var(--sell); + background: var(--sell-container); + color: var(--sell); + font-family: var(--font-editorial); + font-weight: 700; + font-size: 0.9rem; + cursor: pointer; + transition: all 150ms; + letter-spacing: 0.02em; +} +.btn-panic:hover { + background: var(--sell); + color: #fff; + box-shadow: 0 0 20px rgba(255, 61, 90, 0.5); +} + +/* Auth Modal */ +.modal-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + background: rgba(13, 15, 26, 0.8); + backdrop-filter: blur(6px); +} +.modal-card { + background: var(--surface); + border: 1px solid var(--outline); + border-radius: 16px; + padding: 2rem; + width: 100%; + max-width: 400px; + box-shadow: 0 24px 60px rgba(0,0,0,0.5); + position: relative; +} + +/* ============================================================ + MODO CLARO — Paleta Stitch "Digital Curator" + Activado con: document.documentElement.setAttribute('data-theme','light') + ============================================================ */ +[data-theme="light"] { + --bg: #faf8ff; + --surface: #f0ecfe; + --surface-container: #e5e0fa; + --surface-container-low: #ede9fc; + --surface-container-lowest: #ffffff; + --on-background: #1d1a2e; + --on-surface: #5e5a7a; + --outline: #d5cff0; + --outline-faint: #ede8fd; + + --primary: #6549c0; + --primary-light: #7c62d9; + --primary-container: rgba(101, 73, 192, 0.12); + --secondary-container: rgba(101, 73, 192, 0.07); + + --chart-pyth: #6549c0; + --chart-oracle: rgba(101, 73, 192, 0.25); + --chart-grid: rgba(149, 130, 220, 0.15); + --chart-bg: #faf8ff; + + /* buy/sell se mantienen */ + --buy: #00a854; + --buy-container: rgba(0, 168, 84, 0.1); + --sell: #e03351; + --sell-container: rgba(224, 51, 81, 0.1); +} + +/* En light mode ajustamos el header y sidebar que usan --surface */ +[data-theme="light"] .header { + border-bottom-color: var(--outline); +} +[data-theme="light"] .analysis-sidebar { + border-right-color: var(--outline); +} +[data-theme="light"] .order-panel { + border-left-color: var(--outline); +} +[data-theme="light"] .chart-panel { + border-right-color: var(--outline); +} diff --git a/lazer/cardano/pyth-guard/dashboard/src/lib/supabase.ts b/lazer/cardano/pyth-guard/dashboard/src/lib/supabase.ts new file mode 100644 index 00000000..b6fcd4e0 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/lib/supabase.ts @@ -0,0 +1,15 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string; +const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY as string; + +export const supabase = createClient(supabaseUrl, supabaseKey); + +export type Profile = { + id: string; + email: string | null; + full_name: string | null; + avatar_url: string | null; + created_at: string; + updated_at: string; +}; diff --git a/lazer/cardano/pyth-guard/dashboard/src/main.tsx b/lazer/cardano/pyth-guard/dashboard/src/main.tsx new file mode 100644 index 00000000..966f17a4 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/lazer/cardano/pyth-guard/dashboard/src/types/cardano.d.ts b/lazer/cardano/pyth-guard/dashboard/src/types/cardano.d.ts new file mode 100644 index 00000000..85cb3575 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/src/types/cardano.d.ts @@ -0,0 +1,32 @@ +// CIP-30 Cardano dApp Connector — Type Declarations +// Supports: Lace, Nami, Eternl, Flint, etc. + +interface CardanoCIP30 { + apiVersion: string; + name: string; + icon: string; + enable(): Promise; + isEnabled(): Promise; +} + +interface CardanoCIP30API { + getNetworkId(): Promise; // 0 = Testnet, 1 = Mainnet + getUsedAddresses(): Promise; // CBOR hex + getUnusedAddresses(): Promise; + getChangeAddress(): Promise; // CBOR hex + getRewardAddresses(): Promise; + getBalance(): Promise; // CBOR hex (Lovelace) + signTx(tx: string, partialSign?: boolean): Promise; + signData(addr: string, payload: string): Promise<{ key: string; signature: string }>; + submitTx(tx: string): Promise; +} + +interface Window { + cardano?: { + lace?: CardanoCIP30; + nami?: CardanoCIP30; + eternl?: CardanoCIP30; + flint?: CardanoCIP30; + [key: string]: CardanoCIP30 | undefined; + }; +} diff --git a/lazer/cardano/pyth-guard/dashboard/tsconfig.json b/lazer/cardano/pyth-guard/dashboard/tsconfig.json new file mode 100644 index 00000000..3934b8f6 --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/lazer/cardano/pyth-guard/dashboard/tsconfig.node.json b/lazer/cardano/pyth-guard/dashboard/tsconfig.node.json new file mode 100644 index 00000000..0e4095ce --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": false, + "outDir": "./dist" + }, + "include": ["vite.config.ts"] +} diff --git a/lazer/cardano/pyth-guard/dashboard/vite.config.ts b/lazer/cardano/pyth-guard/dashboard/vite.config.ts new file mode 100644 index 00000000..276895ca --- /dev/null +++ b/lazer/cardano/pyth-guard/dashboard/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + strictPort: true, + }, +}); diff --git a/lazer/cardano/pyth-guard/keeper/monitor.ts b/lazer/cardano/pyth-guard/keeper/monitor.ts new file mode 100644 index 00000000..58629f2e --- /dev/null +++ b/lazer/cardano/pyth-guard/keeper/monitor.ts @@ -0,0 +1,285 @@ +/** + * PythGuard Keeper Bot — monitor.ts + * ============================================================ + * Monitorea el precio ADA/USD via Pyth Lazer en tiempo real. + * Cuando el precio cae por debajo del umbral de stop-loss, + * genera el PriceUpdateProof para el contrato Cardano. + * ============================================================ + */ + +import * as dotenv from "dotenv"; +import WebSocket from "ws"; +import path from "path"; + +// Cargar variables de entorno desde ../.env +dotenv.config({ path: path.resolve(__dirname, "../.env") }); + +// ============================================================ +// CONFIGURACIÓN +// ============================================================ + +const PYTH_API_KEY = process.env.PYTH_API_KEY!; +const PYTH_PREPROD_POLICY_ID = process.env.PYTH_PREPROD_POLICY_ID!; +const NETWORK = process.env.NETWORK ?? "Cardano PreProd"; +const STOP_LOSS_THRESHOLD = parseFloat(process.env.STOP_LOSS_THRESHOLD ?? "0.35"); + +// Feed ID de ADA/USD en Pyth Price Feeds +// https://pyth.network/developers/price-feed-ids +const ADA_USD_FEED_ID = + process.env.ADA_USD_FEED_ID ?? + "0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f"; + +// URL del endpoint WebSocket de Pyth Lazer +const PYTH_LAZER_WS = + process.env.PYTH_LAZER_WS_URL ?? "wss://api.pyth.network/ws"; + +// Intervalo de actualización de Pyth Lazer: 400ms +const UPDATE_INTERVAL_MS = 400; + +// ============================================================ +// TIPOS +// ============================================================ + +interface PythLazerPriceUpdate { + type: string; + price_feeds: Array<{ + id: string; + price: { + price: string; + conf: string; + expo: number; + publish_time: number; + }; + ema_price?: { + price: string; + expo: number; + }; + }>; + slot?: number; +} + +interface PriceUpdateProof { + price: number; + rawPrice: bigint; + publishTime: number; + feedId: string; + pythSignature: string; + targetPriceOnChain: bigint; + network: string; + triggeredAt: string; +} + +// ============================================================ +// ESTADO GLOBAL +// ============================================================ + +let lastPrice: number | null = null; +let stopLossTriggered = false; +let updateCount = 0; + +// ============================================================ +// FUNCIONES AUXILIARES +// ============================================================ + +function formatPrice(price: number): string { + return `$${price.toFixed(6)}`; +} + +function normalizePrice(rawPrice: string, expo: number): number { + return parseFloat(rawPrice) * Math.pow(10, expo); +} + +/** + * Genera el PriceUpdateProof compatible con el contrato Aiken. + * En producción, esto se enviaría como redeemer a la transacción Cardano. + */ +function buildPriceUpdateProof( + price: number, + publishTime: number, + rawPrice: string, + expo: number +): PriceUpdateProof { + // Convertir precio a micro-USD (6 decimales) para el contrato + const priceInMicroUSD = BigInt(Math.round(price * 1_000_000)); + // Target price en micro-USD + const targetInMicroUSD = BigInt(Math.round(STOP_LOSS_THRESHOLD * 1_000_000)); + + return { + price, + rawPrice: BigInt(rawPrice), + publishTime, + feedId: ADA_USD_FEED_ID, + pythSignature: PYTH_PREPROD_POLICY_ID, // En producción: firma real del oráculo + targetPriceOnChain: targetInMicroUSD, + network: NETWORK, + triggeredAt: new Date().toISOString(), + }; +} + +/** + * Simula la preparación de la transacción Cardano con el proof. + * En producción: construiría y enviaria la TX via lucid-evolution o cardano-js-sdk. + */ +async function prepareStopLossTransaction(proof: PriceUpdateProof): Promise { + console.log("\n" + "=".repeat(60)); + console.log("🔴 STOP-LOSS ACTIVADO — TRANSACCIÓN PREPARADA"); + console.log("=".repeat(60)); + console.log("📋 PriceUpdateProof:"); + console.log(JSON.stringify(proof, (_, v) => + typeof v === "bigint" ? v.toString() : v, 2)); + console.log("\n📡 Red:", proof.network); + console.log("🔑 Pyth Policy ID:", proof.pythSignature); + console.log("💰 Precio ADA/USD:", formatPrice(proof.price)); + console.log("🎯 Umbral Stop-Loss:", formatPrice(STOP_LOSS_THRESHOLD)); + console.log("⏰ Publicado en:", new Date(proof.publishTime * 1000).toISOString()); + console.log("\n✅ Redeemer listo para enviar al contrato pyth_guard.ak"); + console.log(" → target_price:", proof.targetPriceOnChain.toString(), "micro-USD"); + console.log(" → pyth_signature:", proof.pythSignature); + console.log("=".repeat(60) + "\n"); +} + +// ============================================================ +// LÓGICA PRINCIPAL DEL KEEPER +// ============================================================ + +function processUpdate(update: PythLazerPriceUpdate): void { + const feed = update.price_feeds?.find( + (f) => f.id.toLowerCase() === ADA_USD_FEED_ID.toLowerCase() + ); + + if (!feed) return; + + const price = normalizePrice(feed.price.price, feed.price.expo); + const publishTime = feed.price.publish_time; + updateCount++; + + lastPrice = price; + + // Log compacto cada 5 actualizaciones para no saturar stdout + if (updateCount % 5 === 0) { + const status = price <= STOP_LOSS_THRESHOLD ? "🔴 STOP-LOSS" : "🟢 Protegido"; + process.stdout.write( + `\r[${new Date().toISOString()}] ADA/USD: ${formatPrice(price)} | Umbral: ${formatPrice(STOP_LOSS_THRESHOLD)} | ${status} | Updates: ${updateCount} ` + ); + } + + // Verificar si se activa el stop-loss + if (price <= STOP_LOSS_THRESHOLD && !stopLossTriggered) { + stopLossTriggered = true; + const proof = buildPriceUpdateProof( + price, + publishTime, + feed.price.price, + feed.price.expo + ); + // Fire-and-forget: prepara la transacción + prepareStopLossTransaction(proof).catch(console.error); + } else if (price > STOP_LOSS_THRESHOLD && stopLossTriggered) { + // Resetear si el precio se recupera (trailing stop) + stopLossTriggered = false; + console.log(`\n🟢 Precio recuperado: ${formatPrice(price)} — Stop-Loss desactivado.`); + } +} + +// ============================================================ +// CONEXIÓN WEBSOCKET A PYTH LAZER +// ============================================================ + +function connectToPythLazer(): void { + console.log("🚀 PythGuard Keeper Bot iniciando..."); + console.log(`📡 Red: ${NETWORK}`); + console.log(`🎯 Par: ADA/USD`); + console.log(`⚡ Intervalo Pyth Lazer: ${UPDATE_INTERVAL_MS}ms`); + console.log(`🛡️ Umbral Stop-Loss: ${formatPrice(STOP_LOSS_THRESHOLD)}`); + console.log(`🔑 Pyth Policy ID: ${PYTH_PREPROD_POLICY_ID}`); + console.log("\nConectando a Pyth Lazer...\n"); + + const ws = new WebSocket(PYTH_LAZER_WS, { + headers: { + Authorization: `Bearer ${PYTH_API_KEY}`, + }, + }); + + ws.on("open", () => { + console.log("✅ Conectado a Pyth Lazer WebSocket\n"); + + // Suscribirse al feed ADA/USD + const subscribeMsg = { + type: "subscribe", + ids: [ADA_USD_FEED_ID], + properties: ["price"], + chains: ["EVM"], // Pyth Lazer también expone datos para Cardano via este endpoint + delivery: { + best_effort: { + update_buffer_ms: UPDATE_INTERVAL_MS, + }, + }, + }; + + ws.send(JSON.stringify(subscribeMsg)); + console.log("📩 Suscripción enviada al feed ADA/USD"); + console.log("⏳ Esperando actualizaciones de precio...\n"); + }); + + ws.on("message", (data: WebSocket.RawData) => { + try { + const update: PythLazerPriceUpdate = JSON.parse(data.toString()); + if (update.type === "price_update" || update.price_feeds) { + processUpdate(update); + } + } catch { + // Ignorar mensajes no-JSON (pueden ser pings del servidor) + } + }); + + ws.on("error", (err: Error) => { + console.error("\n❌ Error de WebSocket:", err.message); + console.log("🔄 Reconectando en 3 segundos..."); + setTimeout(connectToPythLazer, 3000); + }); + + ws.on("close", (code: number, reason: Buffer) => { + console.log( + `\n⚠️ Conexión cerrada [${code}]: ${reason.toString() || "Sin motivo"}` + ); + if (code !== 1000) { + console.log("🔄 Reconectando en 3 segundos..."); + setTimeout(connectToPythLazer, 3000); + } + }); + + // Heartbeat cada 30 segundos para mantener la conexión activa + const heartbeat = setInterval(() => { + if (ws.readyState === WebSocket.OPEN) { + ws.ping(); + } else { + clearInterval(heartbeat); + } + }, 30_000); +} + +// ============================================================ +// ENTRYPOINT +// ============================================================ + +// Validar que las variables críticas estén configuradas +if (!PYTH_API_KEY) { + console.error("❌ Error: PYTH_API_KEY no está configurada en el .env"); + process.exit(1); +} + +if (!PYTH_PREPROD_POLICY_ID) { + console.error("❌ Error: PYTH_PREPROD_POLICY_ID no está configurada en el .env"); + process.exit(1); +} + +// Iniciar el keeper +connectToPythLazer(); + +// Graceful shutdown +process.on("SIGINT", () => { + console.log("\n\n🛑 PythGuard Keeper Bot detenido."); + console.log(`📊 Total de actualizaciones procesadas: ${updateCount}`); + console.log(`💰 Último precio ADA/USD: ${lastPrice ? formatPrice(lastPrice) : "N/A"}`); + process.exit(0); +}); diff --git a/lazer/cardano/pyth-guard/keeper/package.json b/lazer/cardano/pyth-guard/keeper/package.json new file mode 100644 index 00000000..24e5f1c7 --- /dev/null +++ b/lazer/cardano/pyth-guard/keeper/package.json @@ -0,0 +1,23 @@ +{ + "name": "pythguard-keeper", + "version": "1.0.0", + "description": "PythGuard Keeper Bot — Stop-Loss Monitor for Cardano using Pyth Lazer", + "main": "dist/monitor.js", + "scripts": { + "dev": "ts-node monitor.ts", + "build": "tsc", + "start": "node dist/monitor.js" + }, + "dependencies": { + "@pythnetwork/pyth-lazer-sdk": "^0.4.0", + "dotenv": "^16.4.5", + "ws": "^8.17.1", + "chalk": "^5.3.0" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "@types/ws": "^8.5.11", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } +} diff --git a/lazer/cardano/pyth-guard/keeper/tsconfig.json b/lazer/cardano/pyth-guard/keeper/tsconfig.json new file mode 100644 index 00000000..46f93b16 --- /dev/null +++ b/lazer/cardano/pyth-guard/keeper/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/requeriments.txt b/requeriments.txt new file mode 100644 index 00000000..ab098780 --- /dev/null +++ b/requeriments.txt @@ -0,0 +1,3 @@ +opshin +pycardano +requests