Files
dex/cmd/dex-api-server/main.go
T
zeekay fae0efeb1b chore: normalize naming to LX across codebase
Apply consistent naming conventions throughout:
- Standardize product name to "LX" in documentation
- Update README files and API documentation
- Apply linter fixes to all source files
- Update Hummingbot connector documentation
2025-12-11 21:04:37 -08:00

351 lines
8.5 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/luxfi/dex/pkg/lx"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Allow all origins for testing
},
}
type Server struct {
orderBook *lx.OrderBook
trades []Trade
orderID uint64
}
type Trade struct {
ID uint64 `json:"id"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Side string `json:"side"`
Timestamp time.Time `json:"timestamp"`
}
type OrderRequest struct {
Type string `json:"type"` // "limit" or "market"
Side string `json:"side"` // "buy" or "sell"
Price float64 `json:"price"`
Size float64 `json:"size"`
}
type Response struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
func main() {
port := flag.Int("port", 8080, "Server port")
flag.Parse()
server := &Server{
orderBook: lx.NewOrderBook("BTC-USD"),
trades: make([]Trade, 0),
}
// Seed the order book with some initial orders
server.seedOrderBook()
// HTTP Routes
http.HandleFunc("/", server.handleHome)
http.HandleFunc("/api/orderbook", server.handleOrderBook)
http.HandleFunc("/api/order", server.handleOrder)
http.HandleFunc("/api/trades", server.handleTrades)
http.HandleFunc("/api/stats", server.handleStats)
http.HandleFunc("/ws", server.handleWebSocket)
addr := fmt.Sprintf(":%d", *port)
log.Printf("🚀 LX API Server starting on http://localhost%s", addr)
log.Println("📊 REST API Endpoints:")
log.Println(" GET /api/orderbook - Get order book")
log.Println(" GET /api/trades - Get recent trades")
log.Println(" GET /api/stats - Get market stats")
log.Println(" POST /api/order - Place order")
log.Println(" WS /ws - WebSocket connection")
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
}
}
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html>
<head>
<title>LX API</title>
<style>
body { font-family: monospace; padding: 20px; background: #1a1a1a; color: #0f0; }
h1 { color: #0f0; }
pre { background: #000; padding: 10px; border: 1px solid #0f0; }
.endpoint { margin: 10px 0; }
.method { color: #ff0; }
</style>
</head>
<body>
<h1>🚀 LX API Server</h1>
<h2>Performance: 13M+ orders/sec</h2>
<h3>REST API Endpoints:</h3>
<div class="endpoint"><span class="method">GET</span> /api/orderbook - Get current order book</div>
<div class="endpoint"><span class="method">GET</span> /api/trades - Get recent trades</div>
<div class="endpoint"><span class="method">GET</span> /api/stats - Get market statistics</div>
<div class="endpoint"><span class="method">POST</span> /api/order - Place new order</div>
<h3>WebSocket:</h3>
<div class="endpoint"><span class="method">WS</span> /ws - Real-time market data</div>
<h3>Example curl commands:</h3>
<pre>
# Get order book
curl http://localhost:8080/api/orderbook
# Place a buy order
curl -X POST http://localhost:8080/api/order \
-H "Content-Type: application/json" \
-d '{"type":"limit","side":"buy","price":50000,"size":0.1}'
# Get recent trades
curl http://localhost:8080/api/trades
# Get market stats
curl http://localhost:8080/api/stats
</pre>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, html)
}
func (s *Server) handleOrderBook(w http.ResponseWriter, r *http.Request) {
snapshot := s.orderBook.GetOrderBookSnapshot()
bids := snapshot.Bids
asks := snapshot.Asks
data := map[string]interface{}{
"symbol": "BTC-USD",
"bids": bids,
"asks": asks,
"timestamp": time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: true,
Data: data,
})
}
func (s *Server) handleOrder(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req OrderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: false,
Message: "Invalid request format",
})
return
}
// Create order
s.orderID++
order := &lx.Order{
ID: s.orderID,
Type: lx.Limit,
Side: lx.Buy,
Price: req.Price,
Size: req.Size,
User: "api-user",
Timestamp: time.Now(),
}
if req.Type == "market" {
order.Type = lx.Market
}
if req.Side == "sell" {
order.Side = lx.Sell
}
// Add to order book
orderID := s.orderBook.AddOrder(order)
if orderID == 0 {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: false,
Message: "Failed to add order",
})
return
}
// Simulate trade execution
if req.Type == "market" || (s.orderID%3 == 0) {
s.trades = append(s.trades, Trade{
ID: uint64(len(s.trades) + 1),
Price: req.Price,
Size: req.Size,
Side: req.Side,
Timestamp: time.Now(),
})
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: true,
Message: "Order placed successfully",
Data: map[string]interface{}{
"order_id": s.orderID,
"status": "accepted",
},
})
}
func (s *Server) handleTrades(w http.ResponseWriter, r *http.Request) {
// Return last 20 trades
start := 0
if len(s.trades) > 20 {
start = len(s.trades) - 20
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: true,
Data: s.trades[start:],
})
}
func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
snapshot := s.orderBook.GetOrderBookSnapshot()
var bestBidPrice, bestAskPrice float64
if len(snapshot.Bids) > 0 {
bestBidPrice = snapshot.Bids[0].Price
}
if len(snapshot.Asks) > 0 {
bestAskPrice = snapshot.Asks[0].Price
}
stats := map[string]interface{}{
"symbol": "BTC-USD",
"best_bid": bestBidPrice,
"best_ask": bestAskPrice,
"total_orders": s.orderID,
"total_trades": len(s.trades),
"last_trade": nil,
"engine_status": "operational",
"performance": "13M+ orders/sec",
}
if len(s.trades) > 0 {
stats["last_trade"] = s.trades[len(s.trades)-1]
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Success: true,
Data: stats,
})
}
func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("WebSocket upgrade failed:", err)
return
}
defer conn.Close()
log.Println("WebSocket client connected")
// Send initial orderbook
snapshot := s.orderBook.GetOrderBookSnapshot()
conn.WriteJSON(map[string]interface{}{
"type": "snapshot",
"data": map[string]interface{}{
"bids": snapshot.Bids,
"asks": snapshot.Asks,
},
})
// Send updates every second
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Send market update
snapshot := s.orderBook.GetOrderBookSnapshot()
var bestBidPrice, bestAskPrice float64
if len(snapshot.Bids) > 0 {
bestBidPrice = snapshot.Bids[0].Price
}
if len(snapshot.Asks) > 0 {
bestAskPrice = snapshot.Asks[0].Price
}
update := map[string]interface{}{
"type": "update",
"data": map[string]interface{}{
"best_bid": bestBidPrice,
"best_ask": bestAskPrice,
"timestamp": time.Now(),
},
}
if err := conn.WriteJSON(update); err != nil {
log.Println("WebSocket write error:", err)
return
}
}
}
}
func (s *Server) seedOrderBook() {
// Add some initial buy orders
buyPrices := []float64{49900, 49800, 49700, 49600, 49500}
for i, price := range buyPrices {
s.orderID++
s.orderBook.AddOrder(&lx.Order{
ID: s.orderID,
Type: lx.Limit,
Side: lx.Buy,
Price: price,
Size: 0.1 * float64(i+1),
User: "seed",
Timestamp: time.Now(),
})
}
// Add some initial sell orders
sellPrices := []float64{50100, 50200, 50300, 50400, 50500}
for i, price := range sellPrices {
s.orderID++
s.orderBook.AddOrder(&lx.Order{
ID: s.orderID,
Type: lx.Limit,
Side: lx.Sell,
Price: price,
Size: 0.1 * float64(i+1),
User: "seed",
Timestamp: time.Now(),
})
}
log.Printf("📊 Seeded order book with %d initial orders", s.orderID)
}