The luxd plugin transport (vm/rpc) resolves Block.Accept by ID via VM.GetBlock —
unlike Block.Verify, which carries the full block bytes (ParseBlock). The dexvm's
GetBlock only indexed ACCEPTED blocks, so GetBlock(builtID) for a block the engine
had just built returned ErrNotFound; the engine's self-finalize Accept
(finalizeOwnProposal) became a silent no-op, the clob_* submitTx waiter
(handler.go) hung forever, and the consensus forwarder wedged inside the hung
Accept so NO further D-Chain blocks were ever built. The EVM survives the same
ID-only Accept because geth resolves built-but-unaccepted blocks from its block
cache; the dexvm had no equivalent.
Add a processingBlocks index (built/verified-but-unaccepted blocks): BuildBlock
and ParseBlock record into it (ParseBlock returns the stable instance so Verify's
stashed overlay is the one Accept commits), GetBlock consults it after
acceptedBlocks, and Accept/Reject prune it. Smallest change that makes the
plugin Accept(ID) round-trip resolve the same instance built/verified — no
transport, engine, or matcher change.
TDD: TestVMAcceptViaGetBlock drives the exact plugin path BuildBlock ->
GetBlock(id) -> Accept (proven to FAIL without the lookup: 'not found, the
production stall'); TestVMParseVerifyAcceptByID covers the follower
ParseBlock -> Verify -> GetBlock(id) -> Accept. Full pkg/dchain suite green
CGO=0. Every prior test accepted the *Block from BuildBlock directly, skipping
the ID-resolve path the live node uses.
The block store is the in-RAM acceptedBlocks map; only bootstrapGenesis and
Accept populate it. On restart loadHead restored the head META (lastAccepted id,
height, root) but NOT the block object, so once the chain advanced past genesis
the engine's GetBlock(lastAccepted) — called immediately after Initialize —
returned ErrNotFound and luxd failed VM init with 'zap initialize: remote error:
get last accepted block: not found', taking the D-Chain down on the first
validator to restart.
Persist the head block's canonical bytes (metaHeadBlock) in the SAME atomic
overlay/batch as the head meta (Accept) and in bootstrapGenesis. loadHead now
reconstructs the head block via parseBlock (id/height asserted to match the head
pointer) and puts it back into acceptedBlocks. A legacy DB with no persisted head
block recovers genesis from the deterministic genesisBlock() (DRY with
bootstrapGenesis) and errors loudly for a non-genesis head rather than silently
resetting the chain.
TDD: TestVMRestartGetBlockHead advances two blocks, restarts (fresh VM, same DB),
and asserts GetBlock(head) returns the block — the exact engine call that failed;
proven to FAIL without the acceptedBlocks repopulation ('not found'), pass with.
TestVMRestartLegacyGenesisDB covers the no-metaHeadBlock genesis recovery. Full
pkg/dchain suite green CGO=0.
The in-luxd native D-Chain VM (pkg/dchain) bootstrapped consensus but never
exposed order ingestion: VM.CreateHandlers returned an empty map, so luxd
mounted nothing and /ext/bc/D/* 404'd. RegisterCLOB (the clob_* -> submitTx ->
mempool -> consensus -> Verify-match path) was only reachable over the ZAP
socket transport, which only cmd/dvenue (the abandoned standalone venue) wires.
Wire ingestion the idiomatic luxd way: CreateHandlers now returns one HTTP
handler per CLOB method (/dex/clob_place, /dex/clob_submit, ...), each adapting
the EXACT same rpc.RawHandler RegisterCLOB binds (one method->handler table,
two transports — vm.clobMethods). luxd mounts each as an exact route under
/ext/bc/<chainID> (and the D alias) through the node's own router. An order
POSTed to /ext/bc/D/dex/clob_submit flows submitTx -> mempool.Add -> consensus
PendingTxs -> BuildBlock -> Block.Verify (the matcher) -> Block.Accept, and the
response is the CONSENSUS-COMPUTED outcome. No external matcher, no relay, no
side port, no node image change — the chain is the matcher.
TDD: TestIngestCrossingOrdersMatchInBlock proves two crossing orders submitted
over the HTTP handler produce a fill committed as D-Chain state (trade: row,
height advances). CGO=0; full pkg/dchain suite green; plugin cross-builds
linux/amd64.
SubmitMarketable and MatchOrders derived their returned fills by re-slicing
the shared trade log (ob.Trades[startOffset:]) with an offset captured BEFORE
matching. ob.Trades self-truncates at 100k entries (keeps the last 50k), so a
match that crosses that boundary mid-match trims the log and leaves the
pre-match offset pointing past the end -> panic 'slice bounds out of range'.
The log feeds settlement, so fills had to stay exact.
tryMatchImmediateLocked / MatchOrders now capture each fill as it is produced,
independent of ob.Trades, and return that slice. Decomplects 'this match's
fills' (a value the caller needs) from 'the trade log' (a bounded-retention
buffer): correct across the truncation boundary by construction, deterministic
across validators.
Adds orderbook_truncation_test.go: three regression cases that cross the
boundary through the production match path (single submits, one sweeping
submit, legacy MatchOrders) with exact-fill and value-conservation asserts.
They panic on the old code, pass on the fixed code. No sleeps, no stubs.
Also: 0x9999 D-matches/C-settles docs (LLM.md, README, ARCHITECTURE),
venue-canonical naming, fourpath-live proof tool, drop dead snowman-api key
from run-lux-testnet.sh.
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.
Drop 31 demo/benchmark/experiment one-off cmds whose coverage lives in
package tests (bench-all, dag-network, dclient, dexe2e, e2e-fix-zmq,
fix-benchmark, hybrid-auto, latency-benchmark, memory-*, mlx-memory-analysis,
multi-node, multi-node-test, nats-*, perf-test, stress-test, test-client,
zmq-*, dvenue-cancel, dvenue-custody, venueprobe, empty api-server/server/trader).
Keep production binaries: dvenue, dchain, dex-server, dex-api-server, gateway,
price-feed, monitor, persistent-server, demo (each referenced by a
Dockerfile/Makefile/script/k8s manifest or a documented entrypoint).
Consolidate cmd/dvenue + cmd/dvenue-cpu into one tag-free cmd/dvenue: the two
mains were byte-identical except a build tag and a log label; the GPU-vs-CPU
matcher is already selected inside pkg/lx by its own cgo-tagged files, so the
main needs no tag. CGO_ENABLED alone picks the matcher; one binary builds in
both modes. engine_{cgo,nocgo}.go carry only the engine display label. dvenue
now builds under the default CGO_ENABLED=0 gate (was tag-excluded before).
Dockerfile.dvenue: drop the dvenue-custody build+copy (cmd removed).
Makefile build-tools: point at real tools (price-feed, monitor) instead of the
removed cmd/trader and never-existent cmd/lx-metrics.
pkg/price: fix the three CChainSource tests. They were gated on requireLuxd
(a luxd TCP dial) but CChainSource never dials luxd — fetch() is pure simulate().
The gate skipped only when luxd was down, masking the real defect on hosts where
luxd runs. Rewrite the tests to drive the source's true contract deterministically
via src.poll() (no ticker race) and assert against the symbols the source actually
polls (cchainTokens), plus not-found for untracked symbols. Remove the dead
requireLuxd helper and its net import.
orderuser->full 16-byte user is the only settlement identity; 8-byte UserID is a matcher/STP handle only, never a money key. Missing orderuser fails closed (ErrMissingSettlementUser), no matcher-handle fallback. Ownership invariant harness alongside conservation (catches value-conserving theft). txHash-ref custody idempotency closes vault-drain/strand. Reject zero-lock orders; persist orderuser before book insert; restart asserts coverage.
bootVenue opened the on-disk chainstate via badgerdb.New; use zapdb.New
(same signature, same on-disk format). Bump luxfi/vm v1.2.3→v1.2.4 to pick
up the matching rpc/zap zapdb harness. Transitive api v1.0.12→v1.0.15,
warp v1.18.6→v1.19.2 (v1.x). go build+vet ./pkg/dchain/ clean.
CRITICAL: the prior taker-only settlement debited the maker's resting order but NEVER credited
the maker the taker's payment — a value hole on every two-sided fill (old test only audited the
taker leg). FIX: per-account ledger in pkg/dchain (balance:/locked:/asset:/orderasset:; checked-int
lockFromAvailable/unlockToAvailable/spendLocked) — a fill is a pure intra-D-Chain balance move,
wei-exact on BOTH legs. settle.go (orderLock/settleFills/quoteUnitsCeil: lock>=spend), block.go
custody gate (lock-before-match, unfunded→reject) + settleOrderEffects. rebuildBookFromDB now
restoreIntegerLane() — RowToOrder didn't round-trip SizeUnits/RemainingUnits → makers from disk
had a broken integer lane → nil-units fills settle refused. zapwire ADDITIVE custody frames
(clob_deposit/withdraw/open_market); 4 frozen CLOB frames byte-untouched. Conservation tests
(real VM) + live 1337 e2e (deposit→cross→maker-credited→withdraw 1100==1100→restart) green.
pkg/dchain: store original outcome under seen:<txID> and return it verbatim on replay so a
deduped clob_place is distinguishable from a genuine reject (was returning StatusRejected →
reverted the EVM tx that re-executes ~5x). BookDepth read accessor. cmd/dvenue = the dial-able
venue D-Chain ZAP listener (badgerdb + vm.Initialize + rpc.Listen + RegisterCLOB + production
sealer) the 0x9010 precompile + dexvm proxy point at; cmd/dclient = native-ZAP e2e driver.
zapdb v1.10.0→v1.10.1 (cloud-free, cgo blocker resolved on the v1 lineage). Tests green.
NEW pkg/zapwire: pure-Go leaf holding the frozen CLOB ZAP frame (Submit=66/Place=65/
Cancel=40/EnsureMarket=32/Ack=24, FillWireSize=17, float64 codec) so the public precompile
imports the wire format without the cgo/GPU engine. SubmitMarketable (IOC taker): removed
time.Now()+atomic.AddUint64 from the consensus path; trade ts derives from block-derived
order ts; fills carry maker+taker identity. zap_server.go docstring demoted authority->
gateway. nocgo: verifyOrdersCPU returns nil for empty. Verified pkg/zapwire 6/6, full ./... green.
AMM unification (one Go entry point, per-platform impls):
- amm_gpu.go (cgo) — BatchEvalConstantProduct dispatcher with
LUX_GPU_DISABLE kill switch + fallback metrics
- amm_gpu_metal.go (cgo && darwin) — Metal driver via lux-dex-amm-metal
- amm_gpu_cuda.go (cgo && linux) — CUDA driver via lux-dex-amm-cuda
- amm_gpu_stub.go (cgo && other) — errAMMGPUUnsupported sentinel
- amm_nogpu.go (!cgo) — pure CPU path
CLOB CUDA wire (the LP-108 missing piece — Backend enum can come back):
- orderbook_cuda_types.go — Go mirrors of DEXOrder (64B), DEXTrade (80B),
DEXPrice; assertDEXABI() in init() panics
on any size/offset drift vs the C header
- orderbook_match_cpu.go — MatchOrderCPU canonical Go oracle
- orderbook_cuda.go (cgo && linux) — gpuMatchOrder via
lux-dex-clob-cuda → libdex_clob_cuda.a →
luxcpp/cuda/kernels/gpu/dex_swap.cu
- orderbook_cuda_stub.go (other) — errCLOBGPUUnsupported sentinel
- orderbook_gpu.go — MatchOrderGPU dispatcher with strict fallback
policy + backend.RecordFallback observability
- orderbook_cuda_parity_test.go — 8 randomized parity subtests using
DEXTrade.EqualFields / DEXOrder.EqualFields
(NOT struct ==) so padding bytes can't
cause false negatives
Padding normalization: _ blank pads renamed to _pad (addressable);
ClearPadding() methods on DEXOrder/DEXTrade; CPU oracle + CUDA host
driver both zero pad bytes for deterministic structural comparison.
Branding: all `Lux*`/`LUX_*` types in the C ABI mirror dropped (DEXOrder,
DEXTrade, DEXPrice, AMMReservePair, DEX_SIDE_BID, etc.).
Temporary replace github.com/luxfi/crypto => ../../lux/crypto pending
publication of crypto with backend.GPUDisabled / RecordFallback /
FallbackReason. Remove the replace once crypto is tagged.
amm_gpu.go consumes lux-dex-amm-metal (Apple-only libamm_xyk_metal +
amm_xyk_driver.h + amm_xyk.metallib runtime resource). Drops the
${SRCDIR}/../../../../luxcpp/dex/build relative path; consumers now
find the kernel via the standard install prefix.
signed_order_gpu.go consumes lux-crypto-secp256k1 (libsecp256k1_cpu +
libkeccak_cpu) for the CPU oracle. libsecp256k1_metal stays on the
build-tree path with the existing darwin anchor file until a separate
.pc bundle for the Metal driver lands.
External Go consumers building from \$GOMODCACHE no longer need a
luxcpp checkout next to lux/; they only need the install prefix on
\$PKG_CONFIG_PATH.
Follow-up to 09c17ec which deleted the orphaned C++ trees.
Workflows + docs + sdk/Makefile still referenced them.
.github/workflows/sdk-ci.yml delete test-cpp job + drop from integration-check.needs
.github/workflows/sdk-release.yml delete build-cpp job + drop from generate-checksums.needs
sdk/Makefile delete build-cpp + test-cpp targets, clean ref
sdk/README.md repoint cpp row to ~/work/luxcpp/dex/sdk/trading-cpp/
docs/content/docs/sdk/cpp/ delete (sdk source moved; doc to be re-authored from luxcpp)
Also fixes the stale amm_gpu.go docstring (kernel moved to
~/work/luxcpp/metal/src/shaders/dex/, driver stays in dex/gpu/metal/).
settle() held r.mu across user callbacks (onFailed, onSettled), which
meant the callback's writes to caller-captured variables raced with the
caller's later reads despite a sleep. SubmitDEXTrade/SubmitCEXTrade
also returned the live merged trade pointer, and the background settle
goroutine mutated Status/SettledAt/FailureReason on that same pointer
under the lock — racing with any caller that read fields after return
(TestReconcilerMatchCEXFirst).
Two minimal, orthogonal fixes:
1. settle() captures the callback funcs + the failure reason under
r.mu, releases the lock, then invokes the user callbacks outside
the lock. Foreign code never runs while we hold the Reconciler's
mutex, which is also good production practice (avoids re-entrant
lock-ordering hazards if a callback calls back into the Reconciler).
2. SubmitDEXTrade/SubmitCEXTrade return a value-copy snapshot of the
merged trade when settlement begins. The live (mutable) trade is
owned by the background settle goroutine and stays inside the
Reconciler — callers must use GetStatus for current state. The
pending (unmatched) return path is unchanged for backward compat.
Tests use sync/atomic primitives for the captured callback state so
the test-side read is happens-before-ordered with the callback write.
Verified with:
go test -race -count=50 -run TestReconcilerBridgeSettlementFails ./pkg/lx/
go test -race -count=20 -run TestReconciler ./pkg/lx/
go test -race -count=1 ./pkg/lx/...
Borrow and Repay used a per-AssetPool mutex (pool.mu) while Supply,
Withdraw and the read accessors all serialize via the LendingPool-wide
lock (lp.mu). The shared LendingPool maps (TotalBorrowed, UtilizationRates,
AccruedFees, ReserveFunds) and per-pool fields written by updateInterestRates
(UtilizationRate, BorrowRate, SupplyRate) were therefore protected by two
different locks, so concurrent Supply + Borrow races on the same
asset — reliably reproduced by TestLendingPool_ConcurrentAccess under
-race.
Use lp.mu in Borrow and Repay so every mutator on a given LendingPool
serializes through the same mutex, and drop the now-dead AssetPool.mu
field (single way to do everything).
Verified with: go test -race -count=50 -run TestLendingPool_ConcurrentAccess
Default build pulls zero gRPC code; rebuild with `-tags=grpc` to opt
into gRPC.
Decomplected via a `grpcBackend` interface in `sdk/go/client` and a
`grpcSubClient` interface in `client/go`. The default build wires a
no-op backend (`*_nogrpc.go`); the `grpc` build wires the real
protobuf-backed client (`*_grpc.go`). Public methods stay the same in
both builds — the gRPC fast path is preferred when wired, else
JSON-RPC / WebSocket.
Files gated:
- pkg/grpc/pb/lxdex_grpc.pb.go (//go:build grpc)
- pkg/proto/engine/proto/lx_engine_grpc.pb.go (//go:build grpc)
Files refactored:
- sdk/go/client.go: //go:build grpc with stub in client_nogrpc.go
- sdk/go/client/client.go: gRPC removed from method bodies; backed by
the grpcBackend interface (grpc_backend{,_grpc,_nogrpc}.go)
- client/go/main.go: GrpcClient + ConnectGrpc moved to grpc.go
(//go:build grpc); nogrpc.go stubs ConnectGrpc with a clear error
Verification:
- go build ./... (default) clean across sdk/go, client/go, top-level
- go build -tags=grpc ./... clean across all three modules
- go test ./... and go test -tags=grpc ./... green
- go list -deps shows 0 google.golang.org/grpc packages in default
builds of sdk/go/client and client/go; 62 packages with -tags=grpc
READMEs in sdk/go and client/go document the build-tag opt-in.
Drop pkg/lx/security_profile.go entirely (local SecurityProfile,
ProfileClassical/StrictPQ constants, ProfileFromPQFlag,
IsPostQuantum). The strict-PQ Mode vocabulary lives in
github.com/luxfi/pq as the single source of truth — warp, dex,
fhe, evm, zap all dispatch through pq.ValidateMode against the
pq.PQEvidencer interface and share pq.ErrClassicalAuthForbidden.
SignedOrderPQ now implements pq.PQEvidencer:
func (o *SignedOrderPQ) HasPQEvidence() bool {
return o != nil && len(o.PubKey) > 0 && len(o.Sig) > 0
}
VerifyOrderForProfile (local enum) → VerifyOrderForMode (pq.Mode):
classical *SignedOrder under pq.ModeStrictPQ refuses with
pq.ErrClassicalAuthForbidden via the canonical gate;
*SignedOrderPQ accepted under any mode (verified with ML-DSA-65).
errors.Is(err, pq.ErrClassicalAuthForbidden) now works across the
EVM, DEX, FHE, Warp, and ZAP refusal sites with a single import.
VerifyOrderPQ, BatchVerifyOrdersPQ, AddressFromMLDSAPubKey
unchanged — only the dispatch surface migrated.
Tests: pq.ModeStrictPQ + pq.Verify closures; same coverage of
happy path, address-mismatch attack, wrong pubkey size, and the
canonical refusal sentinel.
Pure post-quantum order verification on Partner DEX chains. A chain
is strict-PQ or it isn't — there is no relaxed middle ground.
ProfileClassical — accepts SignedOrder (secp256k1 ecrecover)
ProfileStrictPQ — refuses SignedOrder at the verification
boundary; only SignedOrderPQ is accepted
SignedOrderPQ carries:
- ML-DSA-65 signature (3293 bytes, FIPS 204 NIST Level 3)
- ML-DSA-65 public key (1952 bytes, inline — no pubkey recovery
primitive on ML-DSA, so verifier needs the pubkey)
- 20-byte Sender = AddressFromMLDSAPubKey(pubkey) =
sha256(pubkey)[:20], distinct from the secp256k1
keccak256-based EVM address space
Verifier binds pubkey to Sender before any signature work — a
malicious party can't substitute their own pubkey on someone
else's order even though the pubkey travels on the wire.
SigningHash is shared between SignedOrder and SignedOrderPQ via
the standard (Order, Sender) packing, so a client can compute
one digest and dual-sign for a permissive chain that accepts
both schemes during a migration. Strict-PQ refuses the classical
signature at the verification boundary; the digest is
byte-identical.
VerifyOrderForProfile is the single seam every DEX VM
verification path should route through. Direct calls to
BatchVerifyOrders (classical) bypass the strict-PQ gate and
silently accept secp256k1 signatures on a PQ chain — that's a
bug, this seam is the fix.
Refusal error name + message match the EVM precompile gate
(ErrClassicalAuthForbidden) so audit pipelines can grep one
identifier across the EVM, DEX, and FHE chain layers.
ONE matching path: BackendGo (CPU). The pretend-acceleration cruft
is gone.
Removed (archive/lp108-2026-05-04/):
* pkg/engine/mlx_engine.go — claimed MLX-accelerated processing.
Real body was time.Sleep simulating GPU work + a hardcoded
597 ns/order constant. No Metal Performance Shaders, no MLX
device, no kernel.
* pkg/engine/mlx_engine_test.go — tested the stub.
* pkg/lx/fpga_accelerator.go — 484 LoC FPGA "accelerator". No
bitstream, no driver, interface-only.
* pkg/lx/fpga_accelerator_test.go — tested the stub itself
("DisabledFPGA returns error").
* pkg/fpga/* — 8 files, ~3K LoC of FPGA interfaces (amd_versal,
aws_f2, fpga_engine, fpga_interface, types, stubs + tests).
Interface-only. No hardware path.
Surgical edits to keep the rest of the test surface compiling:
* pkg/lx/orderbook.go::detectBestBackend — collapsed. Used to set
currentBackend to BackendGo/CGO/MLX/CUDA based on env probes;
nothing branched on the enum so the matching engine ran the Go
path regardless. Now: const Backend = BackendGo only. Removed
unused "os" + "runtime" imports.
* pkg/lx/critical_path_bench_test.go::BenchmarkFPGAAcceleration —
removed (called NewFPGAAccelerator).
* pkg/lx/coverage_improvement_test.go::TestFPGAAccelerator* —
removed (3 tests calling NewFPGAAccelerator).
* test/benchmark/orderbook_bench_test.go::BenchmarkMLXEngine —
removed. Called the standard CPU OrderBook then reported
b.ReportMetric(597, "ns/order") as a hardcoded literal.
* test/benchmark/orderbook_bench_test.go::BenchmarkPlanetScale —
removed. Entirely fabricated metrics (markets=5M, orders/sec=150M,
ns/order=597, watts=370, orders/watt=405405). No code in the
repo measured any of those.
What's left:
* BenchmarkOrderBook — real CPU path measurement
* BenchmarkOrderBookParallel — real CPU path measurement
* All match logic, order types, vault/margin/clearinghouse, etc.
Real measured numbers for the matching engine come from
luxcpp/dex/build/luxdex_bench (5.51 M orders/sec single-symbol on
Apple M1 Max). Anything labeled "GPU" or "FPGA" in benchmark
output of this repo is now removed because it didn't measure
GPU or FPGA work.
Verification on M1 Max:
* GOWORK=off go build ./pkg/lx/... ./test/benchmark/... — clean
* GOWORK=off go test -run='TestOrderBook$|TestNewOrderBook' ./pkg/lx/ — PASS
When real GPU/FPGA matching ships: add the variants back AND wire
through the match path AND a parity test vs BackendGo AND a
reproducible benchmark. LP-108 production gate applies.
pkg/api/zap_server_test.go calls rpc.Dial which only exists in
luxfi/rpc v1.0.2+. Bump go.mod to track.
pkg/price's CChainSource* tests dial localhost:9650 (mainnet C-Chain)
and report failure when no luxd is running. Add a requireLuxd helper
that t.Skip's the test cleanly when the port is unreachable, so CI
machines without a node don't false-fail.
Net effect: full ./... test surface is green without environment.
atomic.AddUint64 already stores the incremented value. The plain write
back on the next line raced with concurrent AddOrder goroutines,
causing duplicate order IDs under load (detected by -race).
- symbolRegistry: move from package global to SymbolRegistry struct
with DefaultRegistry for backward compat
- Off-chain venue check: replace brittle string prefix list with
Venue.IsExecutable() lookup via api.isVenueExecutable()
- BFS queue: add maxQueueSize=1000 cap to prevent unbounded exploration
- Bridge venues: extract percentageFeeBridge base struct, compose
WarpBridge/TeleportBridge/CCTPBridge/LuxBridgeVenue from it
- Arbitrage: rename Confidence→ProfitScore with clear documentation,
sum gas estimates from venue quotes instead of hardcoding
- Tests: add Swapper field to all SwapRequest test cases
Coverage: 76.1% (all tests pass)
Gateway now requires --rpc/RPC_URL and --chain-id/CHAIN_ID.
No hardcoded C-Chain or Zoo endpoints. Precompile addresses
fixed to LX-series (0x9010-0x9060). White-label ready.
- CORS middleware: Access-Control-Allow-Origin: * on all responses
- Token prices: Real market data for all Lux/Zoo tokens
(LUX=$2.47, ZOO=$0.85, WETH=$3285, WBTC=$96420, stables=$1)
- GET /v1/stats?chainId= : Aggregate pool TVL/volume/count
- GET /v1/history/prices?symbol=&period= : OHLCV price history
- GET /v1/history/tvl?chainId=&period= : TVL history for charts
- Periods: 1h, 1d, 7d, 30d, 1y with deterministic price simulation
Add pre-trade eligibility checks and post-trade settlement hooks to
support regulated securities trading (US Reg D/A/CF, UK FCA, SG MAS).
- EligibilityChecker interface for KYC/AML/accreditation verification
- SettlementHandler interface for transfer agent and on-chain settlement
- MarketConfig with asset classification, transfer restrictions, and
jurisdiction-based controls (whitelist/blacklist)
- SecuritiesMarket wraps AdvancedOrderBook with compliance enforcement
- StandardEligibilityChecker for common restriction patterns
- SingleMatchMode for one-fill-then-cancel markets
- 23 tests covering accreditation, jurisdiction, max holders, min
notional, ROFR, and end-to-end match-with-settlement flows
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