mirror of
https://github.com/luxfi/dex.git
synced 2026-07-27 02:57:58 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -41,7 +41,7 @@ export interface VenueCapabilities {
|
||||
supportedPairs: Set<string>;
|
||||
}
|
||||
|
||||
export function clobCapabilities(): VenueCapabilities {
|
||||
export function orderBookCapabilities(): VenueCapabilities {
|
||||
return {
|
||||
limitOrders: true,
|
||||
marketOrders: true,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user