dex: purge CLOB — DEX (service) + OrderBook (engine)

Decentralized exchange; the engine acts like an order book but is decentralized,
so 'Central Limit Order Book' is a misnomer. Branded/service surface → DEX
(RegisterDEX, DEXMethod*, dex_* wire). Matching engine + GPU → OrderBook
(OrderBookMatch/Source/Arena, source_orderbook.go), preserving the AMM-vs-orderbook
distinction. C-ABI → dex_orderbook_* (matches luxcpp). SDKs/clients/docs aligned.
CGO=0 + cgo builds clean; full suite 23 pkgs green (dchain/dex/lx/fix/gateway/zapwire).
Legit financial-term 'Central Limit Order Book' retained in hummingbot taxonomy.
This commit is contained in:
zeekay
2026-06-26 13:05:07 -07:00
parent 37bcc9fef8
commit c2111dc868
33 changed files with 662 additions and 62 deletions
+600
View File
@@ -0,0 +1,600 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command dexbench is a ZAP load generator and cross-arch determinism checker
// for the standalone D-Chain DEX venue (`dexd run`). It is a CLIENT: it speaks
// the FROZEN dex_* ZAP wire (pkg/zapwire) over github.com/luxfi/rpc, signs every
// money-moving frame exactly as the chains/dexvm proxy and the 0x9010 precompile
// do, and measures the full mempool -> BuildBlock -> Verify -> Accept round trip.
//
// There is no `dexd bench` subcommand in the binary; this is the canonical
// load-gen the testnet harness drives. It is a pure-Go leaf (CGO_ENABLED=0): it
// imports only pkg/zapwire (the frozen wire), github.com/luxfi/crypto (secp256k1
// + keccak), and github.com/luxfi/rpc (the ZAP transport) — never the cgo/GPU
// matcher in pkg/lx, so it builds and runs on any host (the load client need not
// be the venue host).
//
// WIRE/AUTH PARITY — re-defined constants, pinned to source.
// A client cannot import the cgo-tagged pkg/dchain, so (exactly like the proxy
// relay and the precompile adapter) it RE-DEFINES the three small auth pieces.
// They MUST stay byte-identical to pkg/dchain/auth.go + pkg/dchain/tx.go:
//
// - account fold : keccak256(accountDomain ‖ scheme[1] ‖ addr[20])[:16]
// (pkg/dchain/auth.go Account16 / accountDomain)
// - auth digest : keccak256(txAuthDomain ‖ scheme[1] ‖ type[1] ‖ nonce[8] ‖ body)
// (pkg/dchain/auth.go txAuthDigest / txAuthDomain)
// - auth envelope : scheme[1] ‖ nonce[8] ‖ sigLen[2] ‖ pubLen[2] ‖ sig ‖ pub
// (pkg/dchain/auth.go TxAuth.encode)
// - tx-type bytes : ensure_market=1 place=2 cancel=3 submit=4 (pkg/dchain/tx.go)
// - RPC payload : [frozen body] ‖ [auth envelope] (handler.go parseTxFrame)
//
// crypto.Keccak256(a,b,...) hashes the concatenation byte-identically to the
// server's hash.ComputeKeccak256Array — same primitive, so the digests match.
//
// MODES:
//
// dexbench -addrs h1:9099,h1:9100,h2:9099 -conns 32 -duration 30s
// open the venues, fan out -conns signed-write workers per venue, and
// report throughput + p50/p99/p999 latency over the wire.
//
// dexbench -verify -addrs amd64box:9099,arm64box:9099
// drive the IDENTICAL deterministic order sequence into two venues and
// assert byte-identical consensus output (order ids + fills). This is the
// cross-arch (amd64⇄arm64) determinism proof over the live boxes.
package main
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"flag"
"fmt"
"os"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/luxfi/crypto"
"github.com/luxfi/dex/pkg/zapwire"
"github.com/luxfi/rpc"
)
// --- auth constants re-defined from pkg/dchain (see package comment) ----------
const (
txAuthDomain = "lux.dchain.tx.auth.v1" // pkg/dchain/auth.go
accountDomain = "lux.dchain.account.v1" // pkg/dchain/auth.go
schemeSecp = byte(0) // lx.AuthSecp256k1 (pkg/lx/auth.go)
txEnsure = byte(1) // TxEnsureMarket (pkg/dchain/tx.go)
txPlace = byte(2) // TxPlace
txSubmit = byte(4) // TxSubmit
)
// account is one signing identity: a deterministic secp256k1 key, its derived
// 16-byte settlement account (the zapwire user[16] field), and a monotone nonce.
// Each worker owns its own account so its nonce stream is strictly sequential
// (the consensus gate enforces monotone per-account nonces; one in-flight signed
// write per account keeps the stream gap-free).
type account struct {
priv *ecdsa.PrivateKey
user string // string(account16[:]) — the wire user field
nonce uint64
}
// newAccount derives a deterministic identity from a seed label. Deterministic
// derivation is what makes -verify reproducible across hosts: the same label
// yields the same key, account, and signatures on the amd64 and arm64 boxes.
func newAccount(label string) (*account, error) {
d := crypto.Keccak256([]byte("dexbench.secp.v1"), []byte(label))
var priv *ecdsa.PrivateKey
var err error
for { // re-hash on the vanishingly rare out-of-range scalar (matches tests)
priv, err = crypto.ToECDSA(d)
if err == nil {
break
}
d = crypto.Keccak256(d)
}
addr := crypto.PubkeyToAddress(priv.PublicKey).Bytes() // 20-byte keccak address
acc := crypto.Keccak256([]byte(accountDomain), []byte{schemeSecp}, addr)[:zapwire.UserSize]
return &account{priv: priv, user: string(acc)}, nil
}
// sign returns the auth envelope for (typ, nonce, body) under the account key and
// advances the nonce. The envelope is appended to the frozen body to form the RPC
// payload.
func (a *account) sign(typ byte, body []byte) ([]byte, error) {
var n [8]byte
binary.BigEndian.PutUint64(n[:], a.nonce)
digest := crypto.Keccak256([]byte(txAuthDomain), []byte{schemeSecp}, []byte{typ}, n[:], body)
sig, err := crypto.Sign(digest, a.priv) // 65-byte r‖s‖v
if err != nil {
return nil, err
}
env := make([]byte, 0, 1+8+2+2+len(sig))
env = append(env, schemeSecp)
env = append(env, n[:]...)
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(sig)))
env = append(env, l[:]...)
env = append(env, 0, 0) // pubLen = 0 (secp recovers the key)
env = append(env, sig...)
a.nonce++
out := make([]byte, 0, len(body)+len(env))
out = append(out, body...)
return append(out, env...), nil
}
// marketID deterministically maps a market index to a 32-byte poolId. The venue
// accepts any 32-byte poolId; deterministic ids keep -verify reproducible.
func marketID(i int) [32]byte {
var p [32]byte
copy(p[:], crypto.Keccak256([]byte(fmt.Sprintf("dexbench.market.%d", i))))
return p
}
type config struct {
addrs []string
conns int
markets int
duration time.Duration
warmup time.Duration
op string
size float64
price float64
verify bool
dialTO time.Duration
callTO time.Duration
}
func main() {
var (
addrs = flag.String("addrs", "127.0.0.1:9099", "comma-separated venue ZAP endpoints (host:port)")
conns = flag.Int("conns", 16, "signed-write worker connections PER endpoint")
markets = flag.Int("markets", 4, "distinct markets each worker spreads orders across")
duration = flag.Duration("duration", 30*time.Second, "measured load duration")
warmup = flag.Duration("warmup", 3*time.Second, "warmup excluded from stats")
op = flag.String("op", "place", "load op: place (resting write) | cross (place+submit, exercises matcher)")
size = flag.Float64("size", 1.0, "order size")
price = flag.Float64("price", 100.0, "base price")
verify = flag.Bool("verify", false, "determinism mode: drive identical seq into 2 endpoints, compare output")
dialTO = flag.Duration("dial-timeout", 10*time.Second, "per-endpoint dial timeout")
callTO = flag.Duration("call-timeout", 30*time.Second, "per-call timeout")
)
flag.Parse()
cfg := config{
addrs: splitAddrs(*addrs),
conns: *conns,
markets: *markets,
duration: *duration,
warmup: *warmup,
op: *op,
size: *size,
price: *price,
verify: *verify,
dialTO: *dialTO,
callTO: *callTO,
}
if len(cfg.addrs) == 0 {
fmt.Fprintln(os.Stderr, "dexbench: -addrs is required")
os.Exit(2)
}
if cfg.markets < 1 {
cfg.markets = 1
}
if cfg.verify {
if err := runVerify(cfg); err != nil {
fmt.Fprintf(os.Stderr, "dexbench verify: %v\n", err)
os.Exit(1)
}
return
}
if err := runLoad(cfg); err != nil {
fmt.Fprintf(os.Stderr, "dexbench load: %v\n", err)
os.Exit(1)
}
}
func splitAddrs(s string) []string {
var out []string
for _, a := range strings.Split(s, ",") {
if a = strings.TrimSpace(a); a != "" {
out = append(out, a)
}
}
return out
}
// dial opens a ZAP client to addr (the default rpc transport is ZAP).
func dial(ctx context.Context, addr string) (rpc.Client, error) {
return rpc.Dial(ctx, addr)
}
// ensureMarket creates a market (admin frame: body only, no auth envelope).
func ensureMarket(ctx context.Context, c rpc.Client, pool [32]byte) error {
resp, err := c.CallRaw(ctx, zapwire.MethodEnsureMarket, zapwire.EncodeEnsureMarket(pool))
if err != nil {
return err
}
if len(resp) < 9 || resp[8] != zapwire.StatusPlaced {
return fmt.Errorf("ensure_market not placed: %x", resp)
}
return nil
}
// placeSell rests a SELL limit order. In a load made only of resting SELLs there
// is never an opposite bid to cross, so every order rests — a clean, sustainable
// unit for measuring the consensus-write + wire round trip.
func placeSell(ctx context.Context, c rpc.Client, a *account, pool [32]byte, price, size float64) (uint64, error) {
body := zapwire.EncodePlace(pool, zapwire.SideSell, price, size, a.user)
payload, err := a.sign(txPlace, body)
if err != nil {
return 0, err
}
resp, err := c.CallRaw(ctx, zapwire.MethodPlace, payload)
if err != nil {
return 0, err
}
id, status, err := zapwire.DecodeAck(resp)
if err != nil {
return 0, err
}
if status != zapwire.StatusPlaced {
return 0, fmt.Errorf("place rejected: status=%d resp=%x", status, resp)
}
return id, nil
}
// submitBuy crosses the book with a marketable BUY and returns the fills.
func submitBuy(ctx context.Context, c rpc.Client, a *account, pool [32]byte, limit, size float64) ([]zapwire.Fill, error) {
body := zapwire.EncodeSubmit(pool, zapwire.SideBuy, false, limit, size, a.user)
payload, err := a.sign(txSubmit, body)
if err != nil {
return nil, err
}
resp, err := c.CallRaw(ctx, zapwire.MethodSubmit, payload)
if err != nil {
return nil, err
}
return zapwire.DecodeFills(resp)
}
// --- load mode ---------------------------------------------------------------
const sampleCap = 1 << 20 // per-worker latency samples retained for percentiles
type workerStat struct {
ops uint64
errs uint64
fills uint64
latNanos []int64
}
func runLoad(cfg config) error {
fmt.Printf("dexbench load: endpoints=%v conns/endpoint=%d markets=%d op=%s duration=%s warmup=%s\n",
cfg.addrs, cfg.conns, cfg.markets, cfg.op, cfg.duration, cfg.warmup)
type wkr struct {
addr string
idx int
}
var workers []wkr
for _, addr := range cfg.addrs {
for i := 0; i < cfg.conns; i++ {
workers = append(workers, wkr{addr: addr, idx: i})
}
}
stats := make([]workerStat, len(workers))
var started int64
var measuring int32
stop := make(chan struct{})
var wg sync.WaitGroup
dialCtx, dialCancel := context.WithTimeout(context.Background(), cfg.dialTO)
defer dialCancel()
for wi := range workers {
w := workers[wi]
st := &stats[wi]
st.latNanos = make([]int64, 0, 1<<16)
c, err := dial(dialCtx, w.addr)
if err != nil {
return fmt.Errorf("dial %s: %w", w.addr, err)
}
acc, err := newAccount(fmt.Sprintf("%s#%d", w.addr, w.idx))
if err != nil {
return err
}
// Ensure this worker's markets exist (idempotent). For cross mode also seed
// a deep resting SELL the taker submits against.
setupCtx, setupCancel := context.WithTimeout(context.Background(), cfg.callTO)
for m := 0; m < cfg.markets; m++ {
if err := ensureMarket(setupCtx, c, marketID(m)); err != nil {
setupCancel()
return fmt.Errorf("worker %s#%d ensure market %d: %w", w.addr, w.idx, m, err)
}
}
setupCancel()
wg.Add(1)
go func() {
defer wg.Done()
defer c.Close()
atomic.AddInt64(&started, 1)
runWorker(cfg, c, acc, st, &measuring, stop)
}()
}
// Wait for all workers to connect+setup, then run warmup, flip the measuring
// flag, run the measured window, and stop.
for atomic.LoadInt64(&started) < int64(len(workers)) {
time.Sleep(5 * time.Millisecond)
}
if cfg.warmup > 0 {
time.Sleep(cfg.warmup)
}
t0 := time.Now()
atomic.StoreInt32(&measuring, 1)
time.Sleep(cfg.duration)
atomic.StoreInt32(&measuring, 0)
elapsed := time.Since(t0)
close(stop)
wg.Wait()
report(cfg, stats, elapsed)
return nil
}
// runWorker drives signed writes as fast as the venue accepts them (closed-loop:
// one in-flight signed op per account, so the nonce stream stays sequential).
// Latency is recorded only while measuring==1.
func runWorker(cfg config, c rpc.Client, acc *account, st *workerStat, measuring *int32, stop <-chan struct{}) {
var maker *account
if cfg.op == "cross" {
maker, _ = newAccount("maker:" + acc.user)
// Seed a deep resting SELL per market so taker BUYs cross it.
for m := 0; m < cfg.markets; m++ {
ctx, cancel := context.WithTimeout(context.Background(), cfg.callTO)
_, _ = placeSell(ctx, c, maker, marketID(m), cfg.price, cfg.size*float64(1<<20))
cancel()
}
}
mkt := 0
for {
select {
case <-stop:
return
default:
}
pool := marketID(mkt)
mkt = (mkt + 1) % cfg.markets
ctx, cancel := context.WithTimeout(context.Background(), cfg.callTO)
start := time.Now()
var opErr error
var nFills int
switch cfg.op {
case "cross":
fills, err := submitBuy(ctx, c, acc, pool, cfg.price, cfg.size)
opErr = err
nFills = len(fills)
if err == nil && nFills == 0 {
// Liquidity drained: re-seed and retry next loop.
_, _ = placeSell(ctx, c, maker, pool, cfg.price, cfg.size*float64(1<<20))
}
default: // place
_, opErr = placeSell(ctx, c, acc, pool, cfg.price+1, cfg.size)
}
lat := time.Since(start).Nanoseconds()
cancel()
if atomic.LoadInt32(measuring) == 1 {
if opErr != nil {
st.errs++
continue
}
st.ops++
st.fills += uint64(nFills)
if len(st.latNanos) < sampleCap {
st.latNanos = append(st.latNanos, lat)
}
} else if opErr != nil {
// pre/post window error: surface once via errs so a broken run is visible
st.errs++
}
}
}
func report(cfg config, stats []workerStat, elapsed time.Duration) {
var totOps, totErr, totFills uint64
var lat []int64
for i := range stats {
totOps += stats[i].ops
totErr += stats[i].errs
totFills += stats[i].fills
lat = append(lat, stats[i].latNanos...)
}
sort.Slice(lat, func(i, j int) bool { return lat[i] < lat[j] })
thru := float64(totOps) / elapsed.Seconds()
fmt.Printf("\n=== dexbench RESULT ===\n")
fmt.Printf(" op : %s\n", cfg.op)
fmt.Printf(" endpoints : %d (%v)\n", len(cfg.addrs), cfg.addrs)
fmt.Printf(" workers : %d (%d/endpoint)\n", len(cfg.addrs)*cfg.conns, cfg.conns)
fmt.Printf(" elapsed : %s\n", elapsed.Round(time.Millisecond))
fmt.Printf(" ops (acked) : %d\n", totOps)
fmt.Printf(" errors : %d\n", totErr)
if cfg.op == "cross" {
fmt.Printf(" fills : %d\n", totFills)
}
fmt.Printf(" throughput : %.0f ops/sec\n", thru)
if len(lat) > 0 {
fmt.Printf(" latency (µs) : p50=%.1f p90=%.1f p99=%.1f p999=%.1f max=%.1f (n=%d)\n",
pct(lat, 0.50)/1e3, pct(lat, 0.90)/1e3, pct(lat, 0.99)/1e3, pct(lat, 0.999)/1e3,
float64(lat[len(lat)-1])/1e3, len(lat))
}
fmt.Println()
}
// pct returns the p-quantile of a pre-sorted slice (nearest-rank).
func pct(sorted []int64, p float64) float64 {
if len(sorted) == 0 {
return 0
}
i := int(p * float64(len(sorted)))
if i >= len(sorted) {
i = len(sorted) - 1
}
return float64(sorted[i])
}
// --- verify (cross-arch determinism) mode ------------------------------------
// runVerify drives the IDENTICAL deterministic order sequence into every endpoint
// and asserts byte-identical consensus output. Each standalone venue bootstraps
// the same deterministic genesis and runs the same deterministic VM, so identical
// ordered input MUST yield identical order ids and fills regardless of host arch.
// This is the live amd64⇄arm64 determinism proof.
func runVerify(cfg config) error {
if len(cfg.addrs) < 2 {
return fmt.Errorf("verify needs >=2 endpoints (e.g. amd64box:9099,arm64box:9099)")
}
fmt.Printf("dexbench verify: driving identical order sequence into %v\n", cfg.addrs)
ctx, cancel := context.WithTimeout(context.Background(), cfg.callTO*4)
defer cancel()
type conn struct {
addr string
c rpc.Client
// one signing identity per endpoint, derived from the SAME label so the
// signatures (hence tx ids) are identical across endpoints.
maker *account
taker *account
}
var conns []*conn
for _, addr := range cfg.addrs {
c, err := dial(ctx, addr)
if err != nil {
return fmt.Errorf("dial %s: %w", addr, err)
}
defer c.Close()
mk, _ := newAccount("verify.maker")
tk, _ := newAccount("verify.taker")
conns = append(conns, &conn{addr: addr, c: c, maker: mk, taker: tk})
}
pool := marketID(0)
// Deterministic two-sided book then a crossing taker — mirrors the e2e.
type step struct {
kind string // "ensure" | "place" | "submit"
side uint8
price float64
size float64
}
steps := []step{
{kind: "ensure"},
{kind: "place", side: zapwire.SideSell, price: 100.0, size: 10.0},
{kind: "place", side: zapwire.SideSell, price: 101.0, size: 5.0},
{kind: "place", side: zapwire.SideSell, price: 102.0, size: 7.0},
{kind: "submit", side: zapwire.SideBuy, price: 102.0, size: 18.0},
}
// Collect each endpoint's observable output (order ids + fills) per step.
type outcome struct {
ackID uint64
fills []zapwire.Fill
}
results := make([][]outcome, len(conns))
for ci, cn := range conns {
for _, s := range steps {
switch s.kind {
case "ensure":
if err := ensureMarket(ctx, cn.c, pool); err != nil {
return fmt.Errorf("%s ensure: %w", cn.addr, err)
}
results[ci] = append(results[ci], outcome{})
case "place":
body := zapwire.EncodePlace(pool, s.side, s.price, s.size, cn.maker.user)
payload, err := cn.maker.sign(txPlace, body)
if err != nil {
return err
}
resp, err := cn.c.CallRaw(ctx, zapwire.MethodPlace, payload)
if err != nil {
return fmt.Errorf("%s place: %w", cn.addr, err)
}
id, _, err := zapwire.DecodeAck(resp)
if err != nil {
return fmt.Errorf("%s place ack: %w", cn.addr, err)
}
results[ci] = append(results[ci], outcome{ackID: id})
case "submit":
body := zapwire.EncodeSubmit(pool, s.side, false, s.price, s.size, cn.taker.user)
payload, err := cn.taker.sign(txSubmit, body)
if err != nil {
return err
}
resp, err := cn.c.CallRaw(ctx, zapwire.MethodSubmit, payload)
if err != nil {
return fmt.Errorf("%s submit: %w", cn.addr, err)
}
fills, err := zapwire.DecodeFills(resp)
if err != nil {
return fmt.Errorf("%s submit fills: %w", cn.addr, err)
}
results[ci] = append(results[ci], outcome{fills: fills})
}
}
}
// Compare every endpoint against endpoint 0.
base := results[0]
mismatch := 0
for ci := 1; ci < len(results); ci++ {
for si := range base {
a, b := base[si], results[ci][si]
if a.ackID != b.ackID || !fillsEqual(a.fills, b.fills) {
mismatch++
fmt.Printf(" MISMATCH step=%d %s: %s=%+v %s=%+v\n",
si, steps[si].kind, cfg.addrs[0], a, cfg.addrs[ci], b)
}
}
}
fmt.Printf("\n=== dexbench VERIFY ===\n")
fmt.Printf(" endpoints : %v\n", cfg.addrs)
fmt.Printf(" steps : %d\n", len(steps))
// Report the crossing fills observed (same on every endpoint when PASS).
for si, s := range steps {
if s.kind == "submit" {
fmt.Printf(" submit fills @%s : %d fill(s)\n", cfg.addrs[0], len(base[si].fills))
for fi, f := range base[si].fills {
fmt.Printf(" fill %d: %.4f @ %.4f (takerSide=%d)\n", fi, f.Size, f.Price, f.TakerSide)
}
}
}
if mismatch == 0 {
fmt.Printf(" RESULT : PASS — byte-identical consensus output across all endpoints (cross-arch determinism holds)\n\n")
return nil
}
return fmt.Errorf("%d mismatched outputs across endpoints (determinism BROKEN)", mismatch)
}
func fillsEqual(a, b []zapwire.Fill) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
@@ -16,7 +16,7 @@ Our Hummingbot Gateway connector supports all four LX trading schemas:
| **Router** | DEX aggregation | Optimal swap execution, arbitrage |
| **AMM** | Liquidity pools | Market making, LP strategies |
| **CLMM** | Concentrated liquidity | Range orders, active LP management |
| **CLOB** | Order book | Limit orders, market making |
| **OrderBook** | Order book | Limit orders, market making |
## Prerequisites
+2 -2
View File
@@ -181,9 +181,9 @@ After approval:
### vs. Other DEXs
| Feature | LX | AMM DEX | Other CLOB DEX |
| Feature | LX | AMM DEX | Other OrderBook DEX |
|---------|--------|---------|----------------|
| Order Type | CLOB | AMM | CLOB |
| Order Type | OrderBook | AMM | OrderBook |
| Latency | 2ns - 487ns | 2-12s | 100ms-1s |
| Throughput | 434M/sec | N/A | 10K/sec |
| IL Risk | None | High | None |
+3 -3
View File
@@ -38,7 +38,7 @@ bun add @luxfi/trading
```typescript
import { DEX } from '@luxfi/trading';
// Create DEX client (CLOB orderbook exchange)
// Create DEX client (OrderBook orderbook exchange)
const dex = await DEX({ rpcUrl: 'http://localhost:8080/rpc' });
// Place a limit buy order
@@ -76,7 +76,7 @@ import { Client, Config } from '@luxfi/trading';
const config = Config.fromObject({
native: {
'lx-dex': { venueType: 'clob', apiUrl: 'http://localhost:8080/rpc' },
'lx-dex': { venueType: 'orderBook', apiUrl: 'http://localhost:8080/rpc' },
'lx-amm': { venueType: 'amm', apiUrl: 'http://localhost:8080/rpc' }
},
ccxt: {
@@ -177,7 +177,7 @@ The SDK exports the following modules:
| Export | Description |
|--------|-------------|
| `DEX` | Factory for LX DEX CLOB client |
| `DEX` | Factory for LX DEX OrderBook client |
| `AMM` | Factory for LX AMM client |
| `Client` | Unified multi-venue client |
| `Config` | Configuration builder |
@@ -87,7 +87,7 @@ curl http://localhost:15888/lxdex/pairs
| Router | `/lxdex/router` | Quote swap, Execute swap |
| AMM | `/lxdex/amm` | Pool info, Add/Remove liquidity |
| CLMM | `/lxdex/clmm` | Open/Close positions, Collect fees |
| CLOB | `/lxdex` | Place/Cancel orders, Order book |
| OrderBook | `/lxdex` | Place/Cancel orders, Order book |
## Links
@@ -9,7 +9,7 @@ Official Hummingbot Gateway connector for [LX](https://dex.lux.network), the ult
- **Router**: DEX aggregation with optimal swap routing
- **AMM**: Traditional xy=k constant product liquidity pools
- **CLMM**: Concentrated liquidity market maker (Uniswap V3-style)
- **CLOB**: Central limit order book with on-chain settlement
- **OrderBook**: Central limit order book with on-chain settlement
- **Real-Time Updates**: WebSocket subscriptions for order book, trades, and order status
- **Cross-Chain**: Native support for Lux C-Chain and bridged assets
@@ -50,7 +50,7 @@ describe('LXDex Connector', () => {
expect(config.tradingTypes).toContain('ROUTER');
expect(config.tradingTypes).toContain('AMM');
expect(config.tradingTypes).toContain('CLMM');
expect(config.tradingTypes).toContain('CLOB');
expect(config.tradingTypes).toContain('OrderBook');
});
});
@@ -2,7 +2,7 @@
* LX Gateway Connector Configuration
*
* Configuration interface and loader for the LX connector.
* Supports Router, AMM, CLMM, and CLOB trading types.
* Supports Router, AMM, CLMM, and OrderBook trading types.
*/
export interface AvailableNetworks {
@@ -46,7 +46,7 @@ export const LXDexConfigDefaults: LXDexConfig = {
availableNetworks: [
{ chain: 'lux', networks: ['mainnet', 'testnet'] },
],
tradingTypes: ['ROUTER', 'AMM', 'CLMM', 'CLOB'],
tradingTypes: ['ROUTER', 'AMM', 'CLMM', 'OrderBook'],
slippagePct: 0.5,
maxHops: 4,
apiEndpoint: 'https://api.dex.lux.network',
@@ -7,7 +7,7 @@
* Features:
* - Ultra-low latency order matching (<100ns)
* - Multiple orderbook backends (Pure Go, C++, GPU, FPGA)
* - Central Limit Order Book (CLOB) with AMM integration
* - Central Limit Order Book (OrderBook) with AMM integration
* - Concentrated liquidity market making (CLMM)
* - Cross-chain bridge support via Warp messaging
*/
@@ -6,7 +6,7 @@ Official Hummingbot connectors for [LX DEX](https://dex.lux.network).
### 1. LX DEX Spot (`lx_dex`)
Central Limit Order Book (CLOB) connector for spot trading.
Central Limit Order Book (OrderBook) connector for spot trading.
**Features:**
- Limit and market orders
@@ -60,7 +60,7 @@ Enter your wallet address: 0x...
## Strategies
### Pure Market Making (CLOB)
### Pure Market Making (OrderBook)
```yaml
strategy: pure_market_making
exchange: lx_dex
@@ -1,7 +1,7 @@
"""
LX DEX Spot Exchange Connector
Hummingbot connector for LX DEX spot/CLOB trading.
Hummingbot connector for LX DEX spot/OrderBook trading.
Supports limit orders, market orders, and real-time order book updates.
"""
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
class LxDexExchange(ExchangePyBase):
"""
LX DEX Exchange connector for spot/CLOB trading.
LX DEX Exchange connector for spot/OrderBook trading.
This connector enables:
- Limit and market order placement
+6 -6
View File
@@ -43,7 +43,7 @@
}
\title{\textbf{LX: A GPU-Native Decentralized Exchange} \\
\large{Byte-Exact \texttt{uint256} CLOB Matching at 100--250M Fills/s per Device,
\large{Byte-Exact \texttt{uint256} OrderBook Matching at 100--250M Fills/s per Device,
Scaling to Fleet-Level Throughput Through Quantum-Resistant Consensus}}
\author{
@@ -60,7 +60,7 @@ Lux Industries Inc. \\
\begin{abstract}
We present \lxdex{}, a GPU-native decentralized exchange whose central-limit
order book (CLOB) matching kernel computes byte-exact \texttt{uint256} fills.
order book (OrderBook) matching kernel computes byte-exact \texttt{uint256} fills.
On a single accelerator the kernel sustains 104--126M fills/s on an NVIDIA GB10
(6.9\,ns/match) and approximately 250M fills/s on an Apple M4 Max
(4.15\,ns/match); the throughput lever is a Knuth Algorithm-D division that
@@ -87,7 +87,7 @@ The cryptocurrency trading ecosystem faces a fundamental performance barrier: cu
\lxdex{} closes this performance gap, achieving:
\begin{itemize}
\item \textbf{104--126M fills/s} of byte-exact \texttt{uint256} CLOB matching on a single NVIDIA GB10 (6.9\,ns/match), and $\sim$250M fills/s on an Apple M4 Max (4.15\,ns/match)
\item \textbf{104--126M fills/s} of byte-exact \texttt{uint256} OrderBook matching on a single NVIDIA GB10 (6.9\,ns/match), and $\sim$250M fills/s on an Apple M4 Max (4.15\,ns/match)
\item \textbf{$>$1B fills/s} aggregate throughput across a node fleet ($\sim$8--10 accelerators), since per-book arenas are independent --- not from a single device
\item \textbf{2.2M fills/s} market-sharded Block-STM settlement, with a CPU/GPU byte-identical commit order
\item \textbf{Post-quantum rollup} to the Z-Chain: P3Q (ML-DSA-65) at 4{,}714 verifies/s, trustless STARK/FRI (\texttt{starkfri}) at 986 verifies/s
@@ -344,8 +344,8 @@ matching, validated bit-for-bit against the CPU reference oracle.
\toprule
\textbf{Configuration} & \textbf{Throughput} & \textbf{Per-match cost} \\
\midrule
GPU CLOB --- NVIDIA GB10 & 104--126M fills/s & 6.9\,ns \\
GPU CLOB --- Apple M4 Max & $\sim$250M fills/s & 4.15\,ns \\
GPU OrderBook --- NVIDIA GB10 & 104--126M fills/s & 6.9\,ns \\
GPU OrderBook --- Apple M4 Max & $\sim$250M fills/s & 4.15\,ns \\
Settlement (Block-STM, market-sharded) & 2.2M fills/s & --- \\
Fleet aggregate ($\sim$8--10 GPUs) & $>$1B fills/s & --- \\
\bottomrule
@@ -385,7 +385,7 @@ Binance (CEX) & 10M/s & 10ms & No \\
\bottomrule
\end{tabular}
\caption{LX single-device byte-exact matching throughput versus reported
exchange figures. The LX column is measured GPU CLOB matching (GB10 / M4 Max);
exchange figures. The LX column is measured GPU OrderBook matching (GB10 / M4 Max);
fleet aggregation reaches $>$1B fills/s. The latency column is per-match kernel
cost, not end-to-end trade confirmation.}
\end{table}
+4 -4
View File
@@ -44,8 +44,8 @@ pub struct VenueCapabilities {
}
impl VenueCapabilities {
/// Create capabilities for a typical CLOB/orderbook venue
pub fn clob() -> Self {
/// Create capabilities for a typical OrderBook/orderbook venue
pub fn orderBook() -> Self {
Self {
limit_orders: true,
market_orders: true,
@@ -86,7 +86,7 @@ impl VenueCapabilities {
}
}
/// Create capabilities for a hybrid venue (CLOB + AMM)
/// Create capabilities for a hybrid venue (OrderBook + AMM)
pub fn hybrid() -> Self {
Self {
limit_orders: true,
@@ -112,7 +112,7 @@ impl VenueCapabilities {
///
/// This trait provides a consistent API regardless of whether the underlying
/// venue is:
/// - Native LX DEX (CLOB)
/// - Native LX DEX (OrderBook)
/// - Native LX AMM (liquidity pools)
/// - CCXT exchange (Binance, MEXC, OKX, etc.)
/// - Hummingbot Gateway connector
+1 -1
View File
@@ -33,7 +33,7 @@ pub struct CcxtAdapter {
impl CcxtAdapter {
pub async fn new(name: &str, config: CcxtConfig) -> Result<Self> {
let mut capabilities = VenueCapabilities::clob();
let mut capabilities = VenueCapabilities::orderBook();
// CCXT exchanges typically don't support batch orders through unified API
capabilities.batch_orders = false;
+1 -1
View File
@@ -27,7 +27,7 @@ pub async fn create_native_adapter(
config: &NativeVenueConfig,
) -> Result<Arc<dyn VenueAdapter>> {
match config.venue_type.as_str() {
"dex" | "clob" => {
"dex" | "orderBook" => {
let adapter = LxDexAdapter::new(name, config.clone()).await?;
Ok(Arc::new(adapter))
}
+2 -2
View File
@@ -12,7 +12,7 @@ use crate::error::{Error, Result};
use crate::orderbook::Orderbook;
use crate::types::*;
/// LX DEX adapter for CLOB trading
/// LX DEX adapter for OrderBook trading
pub struct LxDexAdapter {
name: String,
config: NativeVenueConfig,
@@ -27,7 +27,7 @@ pub struct LxDexAdapter {
impl LxDexAdapter {
pub async fn new(name: &str, config: NativeVenueConfig) -> Result<Self> {
let mut capabilities = VenueCapabilities::clob();
let mut capabilities = VenueCapabilities::orderBook();
capabilities.streaming = config.ws_url.is_some();
let client = reqwest::Client::builder()
+2 -2
View File
@@ -206,7 +206,7 @@ impl Default for RiskConfig {
/// Native LX venue configuration (lx_dex or lx_amm)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NativeVenueConfig {
/// Venue type: "dex" (CLOB) or "amm" (liquidity pools)
/// Venue type: "dex" (OrderBook) or "amm" (liquidity pools)
#[serde(default = "default_dex_type")]
pub venue_type: String,
@@ -255,7 +255,7 @@ pub struct NativeVenueConfig {
}
impl NativeVenueConfig {
/// Create LX DEX (CLOB) config
/// Create LX DEX (OrderBook) config
pub fn lx_dex(api_url: impl Into<String>) -> Self {
Self {
venue_type: "dex".into(),
@@ -692,8 +692,8 @@ async fn test_unified_client_creation() {
// =============================================================================
#[test]
fn test_venue_capabilities_clob() {
let caps = adapters::VenueCapabilities::clob();
fn test_venue_capabilities_orderBook() {
let caps = adapters::VenueCapabilities::orderBook();
assert!(caps.limit_orders);
assert!(caps.market_orders);
+1 -1
View File
@@ -195,7 +195,7 @@ print(f"Daily PnL: {risk.daily_pnl}")
## Supported Venues
### Native
- LX DEX (CLOB)
- LX DEX (OrderBook)
- LX AMM
### CCXT (100+ exchanges)
@@ -2,7 +2,7 @@
LX Trading SDK - High-frequency trading with unified liquidity aggregation.
Supports:
- Native LX DEX (CLOB) and LX AMM
- Native LX DEX (OrderBook) and LX AMM
- CCXT exchanges (Binance, MEXC, OKX, etc.)
- Hummingbot Gateway connectors
@@ -42,8 +42,8 @@ class VenueCapabilities:
supported_pairs: Set[str] = field(default_factory=set)
@classmethod
def clob(cls) -> "VenueCapabilities":
"""CLOB/orderbook venue capabilities."""
def orderBook(cls) -> "VenueCapabilities":
"""OrderBook/orderbook venue capabilities."""
return cls(
limit_orders=True,
market_orders=True,
@@ -30,7 +30,7 @@ class CcxtAdapter(VenueAdapter):
def __init__(self, name: str, config: CcxtConfig):
self._name = name
self._config = config
self._capabilities = VenueCapabilities.clob()
self._capabilities = VenueCapabilities.orderBook()
self._capabilities.batch_orders = False # CCXT doesn't have unified batch
self._connected = False
self._latency: Optional[int] = None
@@ -30,12 +30,12 @@ from lx_trading.orderbook import Orderbook
class LxDexAdapter(VenueAdapter):
"""LX DEX adapter for CLOB trading."""
"""LX DEX adapter for OrderBook trading."""
def __init__(self, name: str, config: NativeVenueConfig):
self._name = name
self._config = config
self._capabilities = VenueCapabilities.clob()
self._capabilities = VenueCapabilities.orderBook()
self._connected = False
self._latency: Optional[int] = None
self._session: Optional[aiohttp.ClientSession] = None
+1 -1
View File
@@ -257,7 +257,7 @@ console.log(`Kill switch active: ${risk.isKilled}`);
## Supported Venues
### Native
- LX DEX (CLOB)
- LX DEX (OrderBook)
- LX AMM
### CCXT (100+ exchanges)
+1 -1
View File
@@ -41,7 +41,7 @@ export interface VenueCapabilities {
supportedPairs: Set<string>;
}
export function clobCapabilities(): VenueCapabilities {
export function orderBookCapabilities(): VenueCapabilities {
return {
limitOrders: true,
marketOrders: true,
+2 -2
View File
@@ -18,7 +18,7 @@ import {
type Ticker,
type Trade,
} from '../types.js';
import { BaseAdapter, clobCapabilities, type VenueCapabilities } from './base.js';
import { BaseAdapter, orderBookCapabilities, type VenueCapabilities } from './base.js';
// =============================================================================
// CCXT Adapter
@@ -36,7 +36,7 @@ export class CcxtAdapter extends BaseAdapter {
) {
super();
this.capabilities = {
...clobCapabilities(),
...orderBookCapabilities(),
batchOrders: false, // CCXT doesn't have unified batch
};
}
+1 -1
View File
@@ -2,7 +2,7 @@
* Adapter exports.
*/
export { BaseAdapter, clobCapabilities, ammCapabilities, type VenueAdapter, type VenueCapabilities } from './base.js';
export { BaseAdapter, orderBookCapabilities, ammCapabilities, type VenueAdapter, type VenueCapabilities } from './base.js';
export { LxDexAdapter, LxAmmAdapter } from './native.js';
export { CcxtAdapter } from './ccxt.js';
export { HummingbotAdapter } from './hummingbot.js';
+3 -3
View File
@@ -23,10 +23,10 @@ import {
type Ticker,
type Trade,
} from '../types.js';
import { BaseAdapter, clobCapabilities, ammCapabilities, type VenueCapabilities } from './base.js';
import { BaseAdapter, orderBookCapabilities, ammCapabilities, type VenueCapabilities } from './base.js';
// =============================================================================
// LX DEX Adapter (CLOB)
// LX DEX Adapter (OrderBook)
// =============================================================================
export class LxDexAdapter extends BaseAdapter {
@@ -40,7 +40,7 @@ export class LxDexAdapter extends BaseAdapter {
private readonly config: NativeVenueConfig,
) {
super();
this.capabilities = clobCapabilities();
this.capabilities = orderBookCapabilities();
}
async connect(): Promise<void> {
+2 -2
View File
@@ -60,7 +60,7 @@ export {
// Adapters
export {
BaseAdapter,
clobCapabilities,
orderBookCapabilities,
ammCapabilities,
LxDexAdapter,
LxAmmAdapter,
@@ -146,7 +146,7 @@ export interface DexOptions {
}
/**
* Create a simple LX DEX client (CLOB orderbook exchange).
* Create a simple LX DEX client (OrderBook orderbook exchange).
*
* @example
* ```typescript
+1 -1
View File
@@ -6,7 +6,7 @@ vocabulary over the four DEX paths:
| Subpath | Path | Engine source | Use |
|---|---|---|---|
| `@luxfi/dex-sdk/precompile` | V4 on-chain | LXPool precompile `0x9010` (LP-9010) | On-chain settlement via the MIT `@luxfi/exchange` ABIs + viem |
| `@luxfi/dex-sdk/zap` | Binary CLOB wire | `pkg/zapwire` | Lowest-latency take/place |
| `@luxfi/dex-sdk/zap` | Binary OrderBook wire | `pkg/zapwire` | Lowest-latency take/place |
| `@luxfi/dex-sdk/fix` | FIX 4.4 | `pkg/fix` | Institutional order entry |
| `@luxfi/dex-sdk/ws` | JSON WebSocket | `pkg/api` | Streaming market data + orders |
+1 -1
View File
@@ -8,7 +8,7 @@
//
// ./precompile V4 on-chain via LXPool precompile 0x9010 (uses MIT
// @luxfi/exchange ABIs + viem). On-chain settlement.
// ./zap Binary CLOB wire (pkg/zapwire). Lowest-latency take/place.
// ./zap Binary OrderBook wire (pkg/zapwire). Lowest-latency take/place.
// ./fix FIX 4.4 order entry (pkg/fix). Institutional connectivity.
// ./ws JSON WebSocket (pkg/api). Streaming market data + orders.
//
+1 -1
View File
@@ -46,7 +46,7 @@ export enum OrderStatus {
}
/**
* A single market on the CLOB. `id` is the 32-byte pool id used by the engine;
* A single market on the OrderBook. `id` is the 32-byte pool id used by the engine;
* `base`/`quote` are the human symbols (e.g. `LUX`, `LUSD`).
*/
export interface Market {
+8 -8
View File
@@ -1,7 +1,7 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// ZAP path — the binary CLOB wire (`pkg/zapwire`).
// ZAP path — the binary OrderBook wire (`pkg/zapwire`).
//
// ZAP is the low-latency off-chain twin of the on-chain LXPool path: a fixed,
// frozen byte layout the matcher and the d-chain ledger exchange. Frame sizes
@@ -34,12 +34,12 @@ export const WIRE = {
/** ZAP RPC method names (pkg/zapwire). */
export const METHOD = {
EnsureMarket: 'clob_ensure_market',
Place: 'clob_place',
Cancel: 'clob_cancel',
Submit: 'clob_submit',
Deposit: 'clob_deposit',
Withdraw: 'clob_withdraw',
EnsureMarket: 'orderBook_ensure_market',
Place: 'orderBook_place',
Cancel: 'orderBook_cancel',
Submit: 'orderBook_submit',
Deposit: 'orderBook_deposit',
Withdraw: 'orderBook_withdraw',
} as const
export type ZapMethod = (typeof METHOD)[keyof typeof METHOD]
@@ -53,7 +53,7 @@ export interface ZapTransport {
}
/**
* Binary CLOB client. Encodes requests to the frozen wire, decodes responses.
* Binary OrderBook client. Encodes requests to the frozen wire, decodes responses.
* The encode/decode bodies are intentionally left for the implementation pass;
* this scaffold pins the contract, sizes, and method names.
*/