chore: clean up repo structure

- Remove misplaced github.com/luxfi/dex/pkg/grpc/pb directory
- Move nginx config into docker/nginx/
- Consolidate configs/ and node-config/ into config/
- Remove old docker-compose.yml files (use compose.yml)
- Remove oracle/ folder (now native in pkg/price/)
- Remove fake examples/performance_demo.go
This commit is contained in:
Zach Kelling
2025-12-11 18:00:56 -08:00
parent 23fdaa3cac
commit 6c1f6430af
18 changed files with 0 additions and 1111 deletions
-66
View File
@@ -1,66 +0,0 @@
version: '3.8'
services:
dex:
build:
context: .
dockerfile: Dockerfile
container_name: lx-dex
ports:
- "8080:8080" # WebSocket API
- "9090:9090" # REST API
- "50051:50051" # gRPC
environment:
- LOG_LEVEL=info
- ENABLE_METRICS=true
- MAX_CONNECTIONS=10000
- ORDER_BOOK_DEPTH=1000
volumes:
- ./data:/home/dex/data
- ./logs:/home/dex/logs
restart: unless-stopped
networks:
- dex-network
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
prometheus:
image: prom/prometheus:latest
container_name: dex-prometheus
ports:
- "9091:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- dex-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: dex-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
networks:
- dex-network
restart: unless-stopped
networks:
dex-network:
driver: bridge
volumes:
prometheus-data:
grafana-data:
-211
View File
@@ -1,211 +0,0 @@
version: '3.9'
services:
# DEX Backend - Single Node with K=1 Consensus
dex-node:
build:
context: ./backend
dockerfile: Dockerfile
container_name: lux-dex-node
hostname: dex-node-1
environment:
- NODE_ID=node-1
- NODE_PORT=5000
- HTTP_PORT=8080
- WS_PORT=8081
- CONSENSUS_K=1 # Single node consensus
- CONSENSUS_N=1 # Total nodes = 1
- ENGINE_TYPE=hybrid
- TEST_MODE=true
- ENABLE_PERPS=true
- ENABLE_VAULTS=true
- ENABLE_LENDING=true
- LOG_LEVEL=info
- METRICS_ENABLED=true
- METRICS_PORT=9090
ports:
- "8080:8080" # HTTP API
- "8081:8081" # WebSocket
- "5000:5000" # P2P/Consensus
- "9090:9090" # Metrics
volumes:
- dex-data:/data
- ./backend/config:/config
networks:
- dex-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# UI Frontend
dex-ui:
build:
context: ./ui
dockerfile: Dockerfile
args:
- NEXT_PUBLIC_WS_URL=ws://localhost:8081
- NEXT_PUBLIC_API_URL=http://localhost:8080
container_name: lux-dex-ui
environment:
- NODE_ENV=production
- NEXT_PUBLIC_WS_URL=ws://localhost:8081
- NEXT_PUBLIC_API_URL=http://localhost:8080
- NEXT_PUBLIC_BASE_PATH=/v2
ports:
- "3000:3000" # Next.js server
depends_on:
dex-node:
condition: service_healthy
networks:
- dex-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/v2"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: lux-dex-db
environment:
- POSTGRES_DB=luxdex
- POSTGRES_USER=dexuser
- POSTGRES_PASSWORD=dexpass123
- POSTGRES_MAX_CONNECTIONS=200
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./backend/migrations:/docker-entrypoint-initdb.d
networks:
- dex-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dexuser -d luxdex"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Redis Cache
redis:
image: redis:7-alpine
container_name: lux-dex-redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- dex-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Prometheus Metrics
prometheus:
image: prom/prometheus:latest
container_name: lux-dex-prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- "9091:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
networks:
- dex-network
depends_on:
- dex-node
restart: unless-stopped
# Grafana Dashboard
grafana:
image: grafana/grafana:latest
container_name: lux-dex-grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_INSTALL_PLUGINS=grafana-clock-panel
ports:
- "3001:3000"
volumes:
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
- grafana-data:/var/lib/grafana
networks:
- dex-network
depends_on:
- prometheus
restart: unless-stopped
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: lux-dex-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx-cache:/var/cache/nginx
networks:
- dex-network
depends_on:
- dex-ui
- dex-node
restart: unless-stopped
# Test Runner (Optional - for automated testing)
test-runner:
build:
context: ./ui
dockerfile: Dockerfile.test
container_name: lux-dex-tests
environment:
- PLAYWRIGHT_BASE_URL=http://dex-ui:3000
- API_URL=http://dex-node:8080
- WS_URL=ws://dex-node:8081
volumes:
- ./ui/test-results:/app/test-results
- ./ui/playwright-report:/app/playwright-report
networks:
- dex-network
depends_on:
- dex-ui
- dex-node
profiles:
- test
command: npm run test:e2e
networks:
dex-network:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/16
volumes:
dex-data:
driver: local
postgres-data:
driver: local
redis-data:
driver: local
prometheus-data:
driver: local
grafana-data:
driver: local
nginx-cache:
driver: local
-28
View File
@@ -1,28 +0,0 @@
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("🚀 LX DEX Performance Demo")
fmt.Println("===========================")
fmt.Println()
// Simulate performance metrics
fmt.Printf("✅ Order Matching: 13M+ orders/sec @ 75.9ns latency\n")
time.Sleep(500 * time.Millisecond)
fmt.Printf("✅ Trade Execution: 2.1M trades/sec @ 0.63μs latency\n")
time.Sleep(500 * time.Millisecond)
fmt.Printf("✅ Position Updates: 1.57M positions/sec @ 636ns latency\n")
time.Sleep(500 * time.Millisecond)
fmt.Printf("✅ Consensus Finality: 50ms DAG consensus\n")
time.Sleep(500 * time.Millisecond)
fmt.Println()
fmt.Println("🎯 All systems operational at planet-scale performance!")
}
-89
View File
@@ -1,89 +0,0 @@
{
"priceFeedIds": {
"crypto": {
"BTC/USD": "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
"ETH/USD": "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace",
"SOL/USD": "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d",
"BNB/USD": "0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f",
"XRP/USD": "0xec5d399846a9209f3fe5881d70aae9268c94339ff9817e8d18ff19fa05eea1c8",
"ADA/USD": "0x2a01deaec9e51a579277b34b122399984d0bbf57e2458a7e42fecd2829867a0d",
"AVAX/USD": "0x93da3352f9f1d105fdfe4971cfa80e9dd77bfc192d909c4e32ac15a2f358a699",
"DOGE/USD": "0xdcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c",
"DOT/USD": "0xca3eed9b267293f6595901c734c7525ce8ef49adafe8284606ceb307afa2ca5b",
"MATIC/USD": "0x5de33a9112c2b700b8d30b8a3402c103578ccfa2765696471cc672bd5cf6ac52",
"LINK/USD": "0x8ac0c70fff57e9aefdf5edf44b51d62c2d433653cbb2cf5cc06bb115af04d221",
"UNI/USD": "0x78d185a741d07edb3412b09008b7c5cfb9bbbd7d568bf00ba737bf456b5e7c27",
"ATOM/USD": "0xb00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819",
"LTC/USD": "0x6e3f3fa8253588df9326580180233eb791e03b443a3ba7a1d892e73874e19a54",
"NEAR/USD": "0xc415de8d2eba7db216527dff3b60e8410ba5c7f2d87277a4b2c0b75d98cf1cfe"
},
"stocks": {
"AAPL": "0x67a6f93030501a32e15e09e0e61c38c9a04802d11e021a83669350c6fafb419f",
"GOOGL": "0xe65ff435be42630439c96396653a37829c6af7b0f6964a9337f05d79fd0473b7",
"MSFT": "0xd0ca23c1cc9a036b936a76507e9cb26289d4955d3e034e4d20ee8e734e839c69",
"AMZN": "0x9481b13ab3a9a96b90c90efb341bc37cbaa3bbfcb19dc86a1c10dc8e99e38b6d",
"TSLA": "0x16dad506d7db8da01c87581c87ca897a012a153557d4d578c3b9c9e1bc0632f1",
"META": "0x785646804ab62bb37280c8e7c3f5cf66cf5fa683177f16893b4ab685c0402e95",
"NVDA": "0x21198b2cc6eb5772917f8ffaa19056cfc7e8096bb0ee90f227d3e5f64833a57f",
"AMD": "0x5b1703d7eb9dc6b8b5e3c49ff906b5fd539a0c7a0f313b42e60c38583e8ad0f8",
"NFLX": "0xc10a6e9b196fa0b2c7bd2e595502a4dd1cf77ae39e04f55d1c8bb96bdb091730",
"SPY": "0x19e09bb805456ada3979a7d1caca8b470e96698d6be18bfcbbeb87c2cd6e8d45",
"QQQ": "0xb05c02b93c1211a9f96dc98e8e051b8a4bffa87e95e7e2b1d3e32e01797e7f58",
"DIA": "0xd47bf8553c8349f45b283a029126bf7fcb36a1dd08c1fca80ad032c56e696fce",
"JPM": "0x49e3ad9fb0b32a042e8098c5a6f8c88fe1e20f74e10799deb0e742c546f62311",
"BAC": "0xd56339ccf8a5c4e18beec5863a0e0e0487f5920bb0c87b3cd5bb3c21e8e28a9e"
},
"forex": {
"EUR/USD": "0xa995d00bb36a63cef7fd2c287dc105fc8f3d93779f062f09551b0af3e81ec30b",
"GBP/USD": "0x84c2dde9633d93d1bcad84e7dc41c9d56578b7ec52fabedc1f335d673df0a7c1",
"USD/JPY": "0xef23256c1e5917e6672c01c50a4f2e2e88e03e7e8c9caa4cc056a8a2b6c8e4d9",
"USD/CHF": "0x0b1e3297e643f39d15b5d63ddaa49dc95fb2036491e7c9ca504d2b2c6ad712a9",
"AUD/USD": "0x67a6f93030420a32e15e09e061e38c9a048e2d11de210833659350c6fecb520c",
"USD/CAD": "0x1cb0c97a1129c0b240e86351e3f5ec49c6e7886fa44de3e6d8b3645e4e593cc4",
"NZD/USD": "0x92eea9c3b0a2e2e1ffc86cc96a9a96e79312b5f6e3b22c6d5471ed93f91f3865",
"EUR/GBP": "0xc02033c956e96fb8bd1e3e24722c019b07731db5797e2bd5e89d59e02e6b1e0f",
"EUR/JPY": "0xde41cdd3fd5c779f9e0f51e14fd984f7c27e5e9e9e0f73e9a2e5c8a91a1c5290",
"GBP/JPY": "0x7c420ba479bc50cf8c1e46e19cc3b0bbca0b3c0e0b6db3e9f8e2a3fbe1e0e5b2"
},
"commodities": {
"GOLD": "0x765d2ba906dbc32ca17cc11f5310a89e9ee1f6420508c63861f2f8ba4ee34bb2",
"SILVER": "0xf2fb02c32b055c805e7238d628e5e9dadef274376114618e95738502200ef7b0",
"OIL_WTI": "0x30a19158f5a54c0adf8fb7560521343e81e80de53e71cafba56cbee946f36829",
"OIL_BRENT": "0x86443eb8d32c8e8e4bbbb3c1db295d0614e1a8f5e3f19b3b8d92a7e60f36ad1f",
"NATURAL_GAS": "0x21b42ce2bbed07e47bc9d5a427317c2bb0427266ad99a28bd3b97ef072113a01",
"COPPER": "0xa93f354d57859d473b130dc5c1e1e1e1e55dc1c7e78356c8bddcb09f7226e965",
"WHEAT": "0xd415f924f4dc40ae7c87cfccd5e97e0de345f60062e8f96ea086e1f1a2e5cbd8",
"CORN": "0x95241f154d34539741b19ce4bb8a9087b961b8640cb4c0ff0c4020bb5a8fb4eb",
"SOYBEANS": "0x66b70f19c16ed4406e9d5833ce27f2e47c7ec97dc8f652d90bc96797e82638e0",
"COFFEE": "0x72385e670ef5a87dd85e91db088e0a666c996ee0ca2e9bc0fb9f8b6e91e690f1",
"SUGAR": "0x2fb245b9a84554a0f15aa123cbb5f64cd263b59e9a87d80148cbf4caa2ea969d",
"PLATINUM": "0x67c3e8feca77b7077172ef994f8a08c13546c92dc5c8bb558f50ec1fb1c7f8e4",
"PALLADIUM": "0x2fb7c3504f4c7ef30df9a4e3c3e1e8f8a892c01e8d5df8a4dddaaed2d8e91fe8"
},
"indices": {
"S&P500": "0xd47bf8553c8349f45b283a029126bf7fcb36a1dd08c1fca80ad032c56e696fce",
"NASDAQ100": "0x13e3c40ae3ab9c1205339a5dc47e599b05d4277a17e4560e1662e5a1e672e61f",
"DOW30": "0xc74abea3f7b3a0021c7e0585095ffabf3c0eb926145d0229f73cbbc8536cf8c",
"RUSSELL2000": "0xd47bf8553c8349f45b283a029126bf7fcb36a1dd08c1fca80ad032c56e696fce",
"VIX": "0x71334dadd0065bdee3e2ac4e6f3a16247b0836cd7e087b83e45e47c63e812a5a",
"DAX": "0x7df75ceea1bf0e00c695e6c1dcefc13e3d3a1ba93e6a2c7b4bfbc067813e8d8f",
"FTSE100": "0x0e1a9d88a19a06a96e042e5d5a9e92f0b425f879e5e0e2a3e7d5e33e8e4fca45",
"NIKKEI225": "0x6879287e59f18dcb25c0e4d5e11bc8957cbf93ff88e2e2e8f18c2ca6a37e8f3f",
"HSI": "0xfc9e3a70e15720b2e5926e993a2ba4bdf888739f8c67a5ec5889ad1c4e4f0317"
}
},
"updateConfig": {
"minUpdateInterval": 400,
"maxPriceAge": 60000,
"confidenceThreshold": 0.01,
"enableAllAssets": true,
"priorityAssets": [
"BTC/USD",
"ETH/USD",
"SPY",
"AAPL",
"EUR/USD",
"GOLD"
]
}
}
-114
View File
@@ -1,114 +0,0 @@
version: '3.8'
services:
# Lux Oracle - Real-time price feeds powered by Pyth Network
lux-oracle:
image: public.ecr.aws/pyth-network/hermes:latest
container_name: lux-oracle
restart: unless-stopped
environment:
# Network configuration
- PYTHNET_HTTP_ENDPOINT=https://pythnet.rpcpool.com
- PYTHNET_WS_ENDPOINT=wss://pythnet.rpcpool.com
# Hermes configuration
- HERMES_ENDPOINT=0.0.0.0:2000
- HERMES_WS_ENDPOINT=0.0.0.0:2002
- HERMES_METRICS_PORT=8080
# Price feed configuration
- ENABLE_ALL_PRICE_FEEDS=true
- PRICE_SERVICE_URLS=https://xc-mainnet.pyth.network,https://xc-testnet.pyth.network
# Performance tuning
- MAX_MESSAGE_SIZE=104857600
- WS_MAX_FRAME_SIZE=104857600
- CACHE_TTL_SECONDS=1
- UPDATE_INTERVAL_MS=400
# Logging
- RUST_LOG=info
- LOG_FORMAT=json
ports:
- "2000:2000" # HTTP API
- "2002:2002" # WebSocket API
- "8080:8080" # Prometheus metrics
volumes:
- oracle-data:/data
- ./config:/config
networks:
- lx-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2000/api/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 1G
# Lux Price Pusher (optional - for pushing prices on-chain)
price-pusher:
image: public.ecr.aws/pyth-network/price-pusher:latest
container_name: lux-price-pusher
restart: unless-stopped
environment:
- HERMES_URL=http://lux-oracle:2000
- TARGET_CHAINS=ethereum,avalanche,bsc,polygon
- PUSH_INTERVAL_SECONDS=60
- RUST_LOG=info
depends_on:
- lux-oracle
networks:
- lx-network
profiles:
- pusher
# Monitoring for Lux Oracle
prometheus:
image: prom/prometheus:latest
container_name: lux-oracle-prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle'
ports:
- "9091:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
networks:
- lx-network
profiles:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: lux-oracle-grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_INSTALL_PLUGINS=pyth-network-datasource
ports:
- "3001:3000"
volumes:
- ./monitoring/grafana:/etc/grafana/provisioning
- grafana-data:/var/lib/grafana
networks:
- lx-network
depends_on:
- prometheus
profiles:
- monitoring
networks:
lx-network:
external: true
name: lx-backend_lx-network
volumes:
oracle-data:
prometheus-data:
grafana-data:
-34
View File
@@ -1,34 +0,0 @@
{
"name": "@luxexchange/pyth-client",
"version": "1.0.0",
"description": "Pyth Network price feed client for Lux Exchange",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"test": "jest",
"setup": "sh setup.sh",
"start": "docker-compose up -d",
"stop": "docker-compose down",
"logs": "docker-compose logs -f hermes",
"status": "curl http://localhost:2000/api/health",
"test-prices": "ts-node test/test-prices.ts"
},
"dependencies": {
"@pythnetwork/client": "^2.19.0",
"@pythnetwork/hermes-client": "^1.0.0",
"axios": "^1.6.2",
"ws": "^8.15.1",
"decimal.js": "^10.4.3",
"eventemitter3": "^5.0.1"
},
"devDependencies": {
"@types/node": "^20.10.5",
"@types/ws": "^8.5.10",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
-209
View File
@@ -1,209 +0,0 @@
#!/bin/bash
# Lux Oracle Setup Script
# Real-time price feeds for all asset classes
set -e
echo "================================================"
echo " Lux Oracle Setup"
echo "================================================"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# Check Docker
if ! command -v docker &> /dev/null; then
echo -e "${RED}Docker is not installed. Please install Docker first.${NC}"
exit 1
fi
# Check Docker Compose
if ! command -v docker-compose &> /dev/null; then
echo -e "${RED}Docker Compose is not installed. Please install Docker Compose first.${NC}"
exit 1
fi
# Create necessary directories
echo -e "${YELLOW}Creating directories...${NC}"
mkdir -p config monitoring/prometheus monitoring/grafana/dashboards monitoring/grafana/datasources
# Create Prometheus configuration
echo -e "${YELLOW}Creating Prometheus configuration...${NC}"
cat > monitoring/prometheus.yml <<EOF
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'lux-oracle'
static_configs:
- targets: ['lux-oracle:8080']
metrics_path: /metrics
- job_name: 'lx-engines'
static_configs:
- targets:
- 'host.docker.internal:50051' # Go engine
- 'host.docker.internal:50052' # Hybrid engine
- 'host.docker.internal:50053' # C++ engine
- 'host.docker.internal:50054' # Rust engine
EOF
# Create Grafana datasource
echo -e "${YELLOW}Creating Grafana datasource...${NC}"
cat > monitoring/grafana/datasources/prometheus.yml <<EOF
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
EOF
# Create Grafana dashboard for Pyth prices
echo -e "${YELLOW}Creating Grafana dashboard...${NC}"
cat > monitoring/grafana/dashboards/pyth-prices.json <<EOF
{
"dashboard": {
"title": "Lux Exchange - Pyth Price Feeds",
"panels": [
{
"title": "BTC/USD",
"type": "graph",
"targets": [
{
"expr": "pyth_price{symbol=\"BTC/USD\"}"
}
]
},
{
"title": "ETH/USD",
"type": "graph",
"targets": [
{
"expr": "pyth_price{symbol=\"ETH/USD\"}"
}
]
},
{
"title": "Price Feed Latency",
"type": "graph",
"targets": [
{
"expr": "pyth_latency_ms"
}
]
},
{
"title": "Active Subscriptions",
"type": "stat",
"targets": [
{
"expr": "pyth_active_subscriptions"
}
]
}
]
}
}
EOF
# Create environment file
echo -e "${YELLOW}Creating environment configuration...${NC}"
cat > .env <<EOF
# Pyth Network Configuration
PYTHNET_HTTP_ENDPOINT=https://pythnet.rpcpool.com
PYTHNET_WS_ENDPOINT=wss://pythnet.rpcpool.com
# Hermes Configuration
HERMES_ENDPOINT=0.0.0.0:2000
HERMES_WS_ENDPOINT=0.0.0.0:2002
HERMES_METRICS_PORT=8080
# Price Service URLs (Mainnet and Testnet)
PRICE_SERVICE_URLS=https://xc-mainnet.pyth.network,https://xc-testnet.pyth.network
# Performance
UPDATE_INTERVAL_MS=400
CACHE_TTL_SECONDS=1
# Logging
RUST_LOG=info
LOG_FORMAT=json
EOF
# Check if network exists
if ! docker network ls | grep -q "lx-backend_lx-network"; then
echo -e "${YELLOW}Creating Docker network...${NC}"
docker network create lx-backend_lx-network
fi
# Pull latest images
echo -e "${YELLOW}Pulling Docker images...${NC}"
docker pull public.ecr.aws/pyth-network/hermes:latest
# Start Lux Oracle
echo -e "${YELLOW}Starting Lux Oracle...${NC}"
docker-compose up -d lux-oracle
# Wait for Oracle to be ready
echo -e "${YELLOW}Waiting for Lux Oracle to be ready...${NC}"
sleep 10
# Check health
echo -e "${YELLOW}Checking Oracle health...${NC}"
if curl -f http://localhost:2000/api/health > /dev/null 2>&1; then
echo -e "${GREEN}✓ Lux Oracle is healthy${NC}"
else
echo -e "${RED}✗ Oracle health check failed${NC}"
echo "Checking logs..."
docker-compose logs lux-oracle | tail -20
exit 1
fi
# Test price feed
echo -e "${YELLOW}Testing price feeds...${NC}"
BTC_PRICE=$(curl -s "http://localhost:2000/api/latest_price_feeds?ids[]=0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" | jq -r '.[0].price.price' 2>/dev/null || echo "N/A")
if [ "$BTC_PRICE" != "N/A" ] && [ "$BTC_PRICE" != "null" ]; then
echo -e "${GREEN}✓ BTC/USD Price: $BTC_PRICE${NC}"
else
echo -e "${RED}✗ Failed to get BTC price${NC}"
fi
# Optional: Start monitoring
read -p "Do you want to start monitoring? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Starting monitoring stack...${NC}"
docker-compose --profile monitoring up -d
echo -e "${GREEN}✓ Monitoring available at:${NC}"
echo " - Prometheus: http://localhost:9091"
echo " - Grafana: http://localhost:3001 (admin/admin)"
fi
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN} Lux Oracle Setup Complete!${NC}"
echo -e "${GREEN}================================================${NC}"
echo ""
echo "Services running:"
echo " - Oracle API: http://localhost:2000"
echo " - Oracle WebSocket: ws://localhost:2002"
echo " - Metrics: http://localhost:8080/metrics"
echo ""
echo "Available endpoints:"
echo " - Latest prices: http://localhost:2000/api/latest_price_feeds"
echo " - Price at time: http://localhost:2000/api/get_price_feed"
echo " - WebSocket: ws://localhost:2002/ws"
echo ""
echo "To view logs: docker-compose logs -f hermes"
echo "To stop: docker-compose down"
echo ""
-342
View File
@@ -1,342 +0,0 @@
/**
* Pyth Network Price Client for Lux Exchange
* Connects to Hermes node for real-time price feeds
*/
import { EventEmitter } from 'events';
import WebSocket from 'ws';
import axios from 'axios';
export interface PythPrice {
id: string;
price: string;
conf: string;
expo: number;
publishTime: number;
emaPrice: string;
emaConf: string;
status: 'trading' | 'halted' | 'auction' | 'unknown';
numPublishers: number;
maxNumPublishers: number;
attestationTime?: number;
prevPublishTime?: number;
prevPrice?: string;
prevConf?: string;
}
export interface PriceUpdate {
symbol: string;
feedId: string;
price: number;
confidence: number;
timestamp: Date;
emaPrice: number;
status: string;
}
export class PythPriceClient extends EventEmitter {
private hermesUrl: string;
private wsUrl: string;
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
private reconnectDelay = 1000;
private subscriptions: Set<string> = new Set();
private priceFeeds: Map<string, string> = new Map();
private symbolMap: Map<string, string> = new Map();
constructor(
hermesUrl: string = 'http://localhost:2000',
wsUrl: string = 'ws://localhost:2002'
) {
super();
this.hermesUrl = hermesUrl;
this.wsUrl = wsUrl;
this.loadPriceFeedIds();
}
private loadPriceFeedIds(): void {
// Load price feed IDs from config
const config = require('../config/price-feeds.json');
// Map all asset classes
for (const [assetClass, feeds] of Object.entries(config.priceFeedIds)) {
for (const [symbol, feedId] of Object.entries(feeds as any)) {
this.priceFeeds.set(symbol, feedId as string);
this.symbolMap.set(feedId as string, symbol);
}
}
}
/**
* Connect to Hermes WebSocket
*/
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(`${this.wsUrl}/ws`);
this.ws.on('open', () => {
console.log('Connected to Pyth Hermes');
this.reconnectAttempts = 0;
this.emit('connected');
this.resubscribe();
resolve();
});
this.ws.on('message', (data: Buffer) => {
this.handleMessage(data.toString());
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
});
this.ws.on('close', () => {
console.log('Disconnected from Pyth Hermes');
this.emit('disconnected');
this.handleReconnect();
});
} catch (error) {
reject(error);
}
});
}
/**
* Handle incoming price messages
*/
private handleMessage(data: string): void {
try {
const message = JSON.parse(data);
if (message.type === 'price_update') {
const pythPrice = message.price_feed as PythPrice;
const symbol = this.symbolMap.get(pythPrice.id);
if (symbol) {
const update: PriceUpdate = {
symbol,
feedId: pythPrice.id,
price: this.parsePrice(pythPrice.price, pythPrice.expo),
confidence: this.parsePrice(pythPrice.conf, pythPrice.expo),
timestamp: new Date(pythPrice.publishTime * 1000),
emaPrice: this.parsePrice(pythPrice.emaPrice, pythPrice.expo),
status: pythPrice.status
};
this.emit('price', update);
this.emit(`price:${symbol}`, update);
}
}
} catch (error) {
console.error('Failed to parse message:', error);
}
}
/**
* Parse Pyth price with exponent
*/
private parsePrice(price: string, expo: number): number {
const value = parseFloat(price);
return value * Math.pow(10, expo);
}
/**
* Subscribe to price feeds
*/
subscribe(symbols: string[]): void {
const feedIds: string[] = [];
for (const symbol of symbols) {
const feedId = this.priceFeeds.get(symbol);
if (feedId) {
feedIds.push(feedId);
this.subscriptions.add(feedId);
} else {
console.warn(`Unknown symbol: ${symbol}`);
}
}
if (feedIds.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'subscribe',
ids: feedIds
}));
}
}
/**
* Unsubscribe from price feeds
*/
unsubscribe(symbols: string[]): void {
const feedIds: string[] = [];
for (const symbol of symbols) {
const feedId = this.priceFeeds.get(symbol);
if (feedId) {
feedIds.push(feedId);
this.subscriptions.delete(feedId);
}
}
if (feedIds.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'unsubscribe',
ids: feedIds
}));
}
}
/**
* Get latest price via REST API
*/
async getLatestPrice(symbol: string): Promise<PriceUpdate | null> {
const feedId = this.priceFeeds.get(symbol);
if (!feedId) {
throw new Error(`Unknown symbol: ${symbol}`);
}
try {
const response = await axios.get(
`${this.hermesUrl}/api/latest_price_feeds`,
{
params: { ids: [feedId] }
}
);
const priceData = response.data[0];
if (!priceData) return null;
const pythPrice = priceData.price;
return {
symbol,
feedId: pythPrice.id,
price: this.parsePrice(pythPrice.price, pythPrice.expo),
confidence: this.parsePrice(pythPrice.conf, pythPrice.expo),
timestamp: new Date(pythPrice.publish_time * 1000),
emaPrice: this.parsePrice(pythPrice.ema_price, pythPrice.expo),
status: pythPrice.status
};
} catch (error) {
console.error(`Failed to get price for ${symbol}:`, error);
return null;
}
}
/**
* Get multiple prices
*/
async getLatestPrices(symbols: string[]): Promise<Map<string, PriceUpdate>> {
const feedIds: string[] = [];
const symbolToFeedId = new Map<string, string>();
for (const symbol of symbols) {
const feedId = this.priceFeeds.get(symbol);
if (feedId) {
feedIds.push(feedId);
symbolToFeedId.set(feedId, symbol);
}
}
const prices = new Map<string, PriceUpdate>();
try {
const response = await axios.get(
`${this.hermesUrl}/api/latest_price_feeds`,
{
params: { ids: feedIds }
}
);
for (const priceData of response.data) {
const pythPrice = priceData.price;
const symbol = symbolToFeedId.get(pythPrice.id);
if (symbol) {
prices.set(symbol, {
symbol,
feedId: pythPrice.id,
price: this.parsePrice(pythPrice.price, pythPrice.expo),
confidence: this.parsePrice(pythPrice.conf, pythPrice.expo),
timestamp: new Date(pythPrice.publish_time * 1000),
emaPrice: this.parsePrice(pythPrice.ema_price, pythPrice.expo),
status: pythPrice.status
});
}
}
} catch (error) {
console.error('Failed to get prices:', error);
}
return prices;
}
/**
* Get all available symbols
*/
getAvailableSymbols(): string[] {
return Array.from(this.priceFeeds.keys());
}
/**
* Get symbols by asset class
*/
getSymbolsByAssetClass(assetClass: 'crypto' | 'stocks' | 'forex' | 'commodities' | 'indices'): string[] {
const config = require('../config/price-feeds.json');
const feeds = config.priceFeedIds[assetClass] || {};
return Object.keys(feeds);
}
/**
* Handle reconnection
*/
private handleReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.emit('error', new Error('Max reconnection attempts reached'));
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
setTimeout(() => {
console.log(`Reconnecting to Pyth Hermes (attempt ${this.reconnectAttempts})...`);
this.connect().catch(console.error);
}, delay);
}
/**
* Resubscribe after reconnection
*/
private resubscribe(): void {
if (this.subscriptions.size > 0 && this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'subscribe',
ids: Array.from(this.subscriptions)
}));
}
}
/**
* Disconnect from Hermes
*/
disconnect(): void {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
/**
* Check connection status
*/
isConnected(): boolean {
return this.ws?.readyState === WebSocket.OPEN;
}
}
// Export singleton instance
export const pythClient = new PythPriceClient();
-18
View File
@@ -1,18 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}