mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
Adds the missing SERVER side of the FIX trading path and a unified e2e proving REAL fills over all four DEX access paths against ONE book. The decomplection: the D-Chain CLOB (pkg/dchain) is the single matcher, reached over the single frozen clob_* ZAP surface (pkg/zapwire). Every access path is a thin wire dialect onto that surface — none owns a matcher. This commit adds the two missing dialects (FIX, a focused WS venue handler) and the shared seam they translate through. New: - pkg/venue: ONE Venue interface (ensure/place/cancel/submit = the clob_* ops) + ONE production ZAP-backed impl. The seam FIX, WS, and the 0x9010 precompile all route through. Dials the SAME rpc.Listen socket dvenue serves. - pkg/fix: dependency-free FIX 4.4 codec (BodyLength tag 9 + CheckSum tag 10 computed AND validated; spec-correct emit/parse) + a TCP acceptor (Logon/Heartbeat/TestRequest/Logout, monotonic seq, NewOrderSingle->clob_submit->ExecutionReport, OrderCancelRequest-> clob_cancel). Pure protocol front-end onto Venue; no matcher. - pkg/api/venue_ws.go: minimal gorilla/websocket /ws handler that translates a JSON order to Venue.Submit and streams fills back — the WS dialect onto the SAME venue (kept orthogonal to the existing TradingEngine product WS server). - cmd/fix-gateway: runs the acceptor in front of a live venue (-venue clob_* endpoint), the production form of the FIX path. - pkg/dchain/fourpath_e2e_test.go: stands up ONE venue + ONE LUX/LUSD book and drives a real limit-order -> match -> FILL over EACH path (V4 precompile engine_zap frame | ZAP native | FIX | WS) against the SAME resting asks, asserting real consensus fills, per-path custody changes, and GLOBAL conservation (available+locked invariant). V4 leg: exercised at the engine_zap WIRE boundary — the exact 66-byte clob_submit frame engine_zap.Swap emits (asserted byte-identical to zapwire.EncodeSubmit) + the V4 BalanceDelta sign convention derived from the consensus fills. A live luxd EVM (StateDB replay-idempotency, gas, PoolManager) is what the full on-chain precompile call additionally needs; the matcher boundary is proven in-process here. No deps added; no existing files modified (go.mod/go.sum untouched). Builds CGO_ENABLED=0; race-clean.
83 lines
2.8 KiB
Go
83 lines
2.8 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Command fix-gateway runs the FIX 4.4 acceptor as a long-lived TCP server in
|
|
// front of a running D-Chain venue (cmd/dvenue / the node runtime). It is the
|
|
// SERVER side of the FIX trading path: a FIX client (the SDK's EnableFIX(), or
|
|
// any FIX 4.4 engine) connects, logs on, and sends NewOrderSingle / OrderCancel
|
|
// messages; the gateway translates each into the SAME clob_* venue operation that
|
|
// ZAP, WS, and the 0x9010 precompile use, and renders the consensus-computed
|
|
// result back as a FIX ExecutionReport.
|
|
//
|
|
// It owns NO matcher and NO book — it dials the venue's clob_* ZAP endpoint
|
|
// (-venue) and is a pure protocol front-end (pkg/fix + pkg/venue). This is the
|
|
// "one matcher, many protocols" topology: the matcher lives in the venue
|
|
// (pkg/dchain), and fix-gateway is the FIX dialect onto it, exactly parallel to
|
|
// the WS and precompile front-ends.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/luxfi/dex/pkg/fix"
|
|
"github.com/luxfi/dex/pkg/venue"
|
|
log "github.com/luxfi/log"
|
|
)
|
|
|
|
func main() {
|
|
addr := flag.String("addr", "127.0.0.1:5001", "TCP listen address for the FIX acceptor")
|
|
venueAddr := flag.String("venue", "127.0.0.1:9099", "ZAP endpoint of the D-Chain venue (clob_*)")
|
|
target := flag.String("target", "LXDEX", "this acceptor's CompID (the client's TargetCompID)")
|
|
heartBt := flag.Int("heartbeat", 30, "heartbeat interval advertised on Logon (seconds)")
|
|
dialTimeout := flag.Duration("dial-timeout", 10*time.Second, "venue dial timeout")
|
|
flag.Parse()
|
|
|
|
logger := log.Root()
|
|
|
|
// Dial the venue's clob_* surface — the SAME socket the precompile and proxy
|
|
// dial. The gateway holds this one connection and routes every FIX order
|
|
// through it.
|
|
dialCtx, dialCancel := context.WithTimeout(context.Background(), *dialTimeout)
|
|
v, err := venue.DialVenue(dialCtx, *venueAddr)
|
|
dialCancel()
|
|
if err != nil {
|
|
logger.Error("dial venue", "venue", *venueAddr, "err", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
acc, err := fix.NewAcceptor(fix.Config{
|
|
Addr: *addr,
|
|
Venue: v,
|
|
TargetCompID: *target,
|
|
HeartBtInt: *heartBt,
|
|
Logger: logger,
|
|
})
|
|
if err != nil {
|
|
logger.Error("new acceptor", "addr", *addr, "err", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Graceful shutdown on SIGINT/SIGTERM.
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sig
|
|
logger.Info("fix-gateway shutting down")
|
|
cancel()
|
|
}()
|
|
|
|
logger.Info("fix-gateway up", "addr", acc.Addr(), "venue", *venueAddr, "target", *target, "heartbeat", *heartBt)
|
|
if err := acc.Serve(ctx); err != nil {
|
|
logger.Error("acceptor serve", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|