mirror of
https://github.com/luxfi/dex.git
synced 2026-07-27 02:57:58 +00:00
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:
@@ -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:
|
||||
@@ -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
|
||||
@@ -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!")
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
@@ -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 ""
|
||||
@@ -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();
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user