mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
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
351 lines
8.5 KiB
Go
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)
|
|
} |