35 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 996677759a One request the edge permits was a thousand questions it did not
A price is a fan-out. The edge limits requests — thirty a second from one
address — and each may name fifty tokens, read at two sizes against two
numéraires across every arm. Nothing bounded what came out the other side, and
what comes out lands on public endpoints we hold no account with; being refused
there takes quoting down for everybody, on every chain.

So the budget is sixteen questions in flight against one chain for the whole
process, rather than eight per request multiplied by however many requests
arrive. It is taken around a single reading and not around a whole price,
because a price against the hub waits for the hub's own price, and a slot held
across that wait deadlocks the first time every slot is held by something
waiting on the hub.

Measured: one request of 50 cold Ethereum tokens went from 10.1s to 8.6s, and
four chains of 40 at once from 10.7s to 9.3s — and returned the same counts on
a repeat, where the same burst before this returned a different set of prices
each time.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 12:31:14 -07:00
zeekayandHanzo Dev f7a7c12d9a A pool with nothing left in it answers, and a rounding error prices in millions
The first cut read each market once, at a hundred dollars. That is right for a
pool with depth and nonsense for one without: a hundred dollars into an
abandoned pool buys a rounding error, and the implied price of a rounding error
is enormous. Measured over the fifty tokens Ethereum lists first, half of what
came back was more than one percent off an independent source and AUCTION — a
token worth $3.44 — read $889,434,176.

So the pools are asked twice, at ten dollars and at a hundred, and a price that
does not survive ten times the size is not returned at all. Twenty-five of the
twenty-seven that now come back agree with that source within two percent. The
two that do not are real pools here sitting off the wider market, and that is
this venue's price: /v1/trade/quote for the same size implies $0.036806 for ASM
where /v1/trade/price says $0.036834, so the screen and the swap panel agree,
which is the thing that has to be true.

The gate is on the best reading at each size, not on each numéraire separately.
Gating per numéraire refuses the cheapest route for being thin and then quotes a
dearer one that was not — ANT came back at $3.72 against a market of $0.07 that
way, because the route that knew better had been thrown out.

Both sizes are asked at once, so the second one costs calls and not latency:
fifty cold tokens on Ethereum in 10.1s, the same fifty warm in 36ms.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 12:20:28 -07:00
zeekayandHanzo Dev c07d15dc5a LLM.md described a gateway with paths the binary refuses
A second account of the trading surface sat further down the file and
contradicted the first: pools and order among the routes, a provider registry
where Uniswap is a fallback on every chain rather than something registered only
when a key exists, a native provider of stubs against a precompile at 0x0400,
and a future-work list asking for the quote and token-list caching that is now
in the binary. A document that disagrees with itself is worse than a shorter
one. The account at the top is the one held to the route table by a test.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 12:04:51 -07:00
zeekayandHanzo Dev 4c591bfc94 A chain with no indexer still knows its own tokens and what they are worth
Explore on Ethereum drew nothing at all — not an empty table, no request made
— because enumerating a chain went to an indexer that only carries ours, and
the hosted API that would answer costs a contract. Two paths close that without
asking anybody for one.

/v1/trade/tokens serves a list the binary carries: 935 tokens over the seven
chains this gateway prices, identity only, checked at build time rather than
per request. No logo, and the struct has nowhere to put one — every public list
gives logos as URLs on four hosts belonging to somebody else, fetched by every
viewer's browser on every market screen.

/v1/trade/price is a reading of the same pools /v1/trade/quote reads: spend a
fixed hundred dollars of a numéraire, see what comes back, put the pool's fee
back in. Not one whole token — that is a fifty-cent trade in one market and a
hundred-thousand-dollar one in the next, and the second moves the pool it is
measuring. Not the raw quote either: a pool hands back the mid less its tier,
which is $1.0031 for a $1.00 token and $3018.51 for a $3000 one two hops deep.
Two numéraires per chain, the dollar and the wrapped native token, and the
cheaper reading wins. Measured against an independent source on seven chains,
every price inside half a percent.

A token no pool holds comes back WITHOUT priceUSD, never with a zero: a screen
can draw a dash and it cannot un-draw a wrong number. A reading stands for a
minute; an unreadable chain for ten seconds, because the pools have not changed
— our access to them has. Fifty per request, eight in flight, and thirty-two
readers of one cold price produce one fan-out.

Two things this needed that were wrong on their own account. A fee had two
units — the V2 arm answered 30 where the V3 arm answered 3000 for the same
three tenths of a percent — so feeFromBPS converts once and nothing else means
anything else by that number. And the arm reading our own precompiles answered
"no liquidity" to an endpoint that never replied, which is enough on its own to
report a chain nobody could reach as a pair nobody holds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 11:52:57 -07:00
zeekayandHanzo Dev f82a8aace1 A dead endpoint is not an empty pool
Both came out as an empty list, so WETH/USDC on Ethereum — one of the deepest
pools in existence — was reported as "no venue here holds this pair" whenever
its RPC was down. That sends a reader looking for liquidity instead of at their
endpoint, which is the same mistake as blaming the pair for an exact-output
request and blaming an upstream registry for our own chain.

The venues already distinguished the two answers: no liquidity is (nil, nil), a
chain they could not reach is (nil, err). QueryAllVenues discarded both. It now
returns the reason, and only when EVERY arm it asked failed — one arm down
while another answers is a quote, not an outage. It also stops returning the
routing label, which named V4_NATIVE for a V3 pool on somebody else's chain and
was read by one test assertion and nothing else.

Underneath, EVMClient marks the answers it never received: a connection
failure, a 5xx, a 429, a body that is not JSON-RPC. A contract answering by
REVERTING stays what it always was — this pool does not exist — because three
of the four V3 fee tiers revert on most pairs and that is the ordinary case.

Measured with chain 1 pointed at a closed port: chain 1 answers 502 naming the
dial error, 96369 still quotes 997001483075056712, a pair with no pool still
answers 404, and /providers stays 200 so one dark chain does not take a pod out
of its own Service.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 01:45:45 -07:00
zeekayandHanzo Dev b6d25b7de2 Record what 96369 actually carries, and which arm answers
The v4_native arm is configured on 96369 and silent: 0x9010 answers 0x there,
measured with the venue's own quoter selector, so every price on that chain
today comes from uniswap_v3 reading the QuoterV2 the venue deployment put
there. `native: true` says the arm is attached, not that it answered.

Also the addresses from luxfi/standard deployments/venue/96369.json, the reason
its `Router` key belongs to LUX_V3_ROUTER and never LUX_V2_ROUTER, and one
measured price per configured chain so a later reader can tell a broken arm
from a moved market.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 01:39:07 -07:00
zeekayandHanzo Dev 5b9ac470d9 The deadline is honoured on every arm, not only the V2 one
SwapRouter02 dropped the deadline from exactInputSingle's tuple and takes it
through multicall(uint256,bytes[]) instead, which is how Uniswap's own
interface sends one. Without that wrap a deadline handed to /v1/trade/swap was
accepted and silently dropped on every V3 route while the V2 route honoured it
— one field meaning two things depending on which pool happened to win the
quote.

amountOutMinimum bounds the price; the deadline bounds the time. A signed swap
that sits in a mempool and lands an hour later still passes its floor, at a
price nobody would choose now. Unset means twenty minutes out, because zero is
a deadline in 1970 and every swap would revert as too old.

Measured against 96369's live SwapRouter02, both directions:
  deadline ahead of block.timestamp -> 997001483075056712, the quoted amount
  deadline behind it                -> execution reverted: Transaction too old

Worth knowing for anything that tests this: 96369 seals a block when there is a
transaction, so block.timestamp ran 4709 seconds behind wall clock while this
was measured. A deadline computed from wall clock is generous here rather than
tight — the safe direction, but `now - 60` will not read as expired.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 01:17:20 -07:00
zeekayandHanzo Dev fb30703324 A tolerance finer than a basis point was answered with no protection at all
leastAccepted computed the swap floor in whole basis points, so a tolerance
below 0.01% truncated to zero and amountOutMinimum came back EQUAL to the
quote. A caller asking for very tight protection got none: the router reverts
on the first wei of movement, including the movement their own trade causes.
The arithmetic is in hundredths of a basis point now, which is the unit a V3
fee tier is already stated in. Measured on 96369 against 997001483075056712:
slippage 0.005 gives 996951633000902959, four nines of the quote, where it used
to give the quote itself.

A direction is not a missing market. getAmountsOut and quoteExactInputSingle
price a known input and have no other direction, so an exact-output request
gets nothing back however deep the pool is — and "no venue here holds this
pair" sent someone looking for liquidity sitting right there. It says which of
the two it was, and what to ask instead. Named as the likely cause rather than
asserted, because a deployment carrying an arm that prices both ways reaches
that line only when the pair really is unheld.

A quote that named no pool built a swap against Route[0] of an empty slice.
venueQuotes always writes one hop, so this was reachable only by changing that
function — which is exactly when a panic in the swap path would be found in
production.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 01:07:10 -07:00
zeekayandHanzo Dev e0fde794da The venue that quotes is the venue that builds, and every path answers
/v1/trade/swap asked the hosted provider registry for a price. This deployment
registers none on purpose, so it answered 500 while /v1/trade/quote priced the
same pair from the pools a line away — and then encoded whatever came back for
the PoolManager precompile at 0x9010 regardless of where the price came from. A
router on Ethereum quoted and a precompile on our chain addressed, in one
response. 0x9010 answers 0x on 96369, measured with the venue's own quoter
selector, so the calldata named a contract that is not there.

Venue has a Swap method now. Each one builds against the contract it read: the
V3 arm against the SwapRouter02 beside its quoter, the V2 arm against the same
Router02 that answered getAmountsOut. A venue that prices a market it cannot
settle returns nothing, and the surface says so naming it.

The selector was wrong in the way that spends money. calldata_lxrouter.go sent
04e45aaf under a signature it called ExactInputSingle(address,address,uint256,
uint256,uint160,bytes32); that hashes to f48f227e. 04e45aaf is SwapRouter02's
exactInputSingle((address,address,uint24,address,uint256,uint256,uint160)) — a
seven-field tuple. A router handed the six-field form reads amountIn where the
fee tier belongs and amountOutMinimum where the recipient belongs. Those
builders were reachable only from their own tests; the padding and decoding
survive in abi.go.

Proved end to end: the gateway's own calldata replayed through eth_call against
96369's real SwapRouter02, from the account holding the allowance, returns
997001483075056712 — the number the quote gave. The tier travels on
route[0].fee because a V3 quote asks four tiers and keeps one, and a swap that
does not name the same pool executes at a different price. amountOutMinimum is
99.5% of the quote by default, because a router handed the quoted amount as its
floor reverts on the movement the caller's own trade causes.

Permit2 is at one address everywhere it exists and does not exist everywhere:
9152 bytes on Ethereum, nothing on 96369. Both paths check first, so our chain
gets a sentence instead of calldata addressed to an empty account.

Nine more paths are gone because they could not work here. Five answered 500
"no providers available" — tokens, pools, pool/, positions, stats, price,
prices — because enumerating a chain is an index read and this reads one pool
by address, right now. api-explore.lux.cloud already answers all of it: 16
pools, $39.9M of TVL, real symbols and decimals for 96369. /v1/route is the
same read in a hat and returned {"routes":[]} forever, which a caller reads as
"no path exists" rather than "I cannot answer". The order paths were an
in-memory map behind two replicas, so an order placed on one pod was a 404 on
the other about half the time and every order died on restart — the D-Chain
venue is the order book and it is deployed beside this. The position builders
all addressed 0x9010.

Eight paths remain and all eight were exercised against the live chain.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 01:01:50 -07:00
zeekayandHanzo Dev 8cc08f3b69 One trading surface, at one name, describing itself truthfully
Three things claimed to be this repo's trading API and no two agreed.

pkg/trading carried its own Venue interface, its own V4/native/V2/V3 and broker
venues, its own constant-product math and its own eth_call client — a copy of
what pkg/gateway already had — and nothing mounted it: grep for the import
outside that directory returns empty. api/openapi.yaml documented it anyway, so
the one published contract in the repo described the one package no binary
served. Meanwhile the binary answered at /v1/* and again at /trading/* in
Uniswap's shapes: two contracts for one API, reachable by both names, free to
drift. Deleted, both of them, rather than picked between and left beside.

Everything answers under /v1/trade now. api.lux.cloud carries the chain at
/v1/chain, the C-chain at the root and other services besides, so /v1/quote was
a generic name claimed on a shared host; one prefix is one namespace with one
owner, and the edge rule that publishes it collapses from a nine-clause
allowlist to a prefix. Server.routes() is the surface written once, and
TestTheDocumentIsTheSurface reads api/openapi.yaml and holds the two to each
other in both directions.

/v1/trade/venues is new. It is the question a screen asks before it asks
anything else — which chains this deployment can price, and from what — grouped
by chain, because a venue is a contract on a chain and the same name means a
different pool on each. Without it a client learns a chain is unquotable by
quoting on it and reading an error.

The allowance is read rather than assumed. approval/check and permit2/check
answered allowance 0 and needsApproval on every request, under a comment saying
the gateway had no way to reach a chain; it has one per chain, one field away —
the same endpoint the pool is quoted from. Measured against 96369: LUSD to the
position manager reads 6000000000000000000000000, which is what eth_call
returns for that pair, and the approve tx now rides along only when it is
needed. Handed back beside "you already approved" it is a transaction somebody
sends, and the user pays gas for a state change that changes nothing before
every swap. Permit2 reads the expiration too, because a grant that has run out
is not a grant.

Nothing here invents a number any more. /v1/history/prices and /v1/history/tvl
returned a random walk seeded from hardcoded base prices — LUX at 2.47, WBTC at
96420, 12.5M of TVL on a chain whose pools were seeded hours ago — and both
were routed to the public internet, where a chart drawn from them is
indistinguishable from a chart of the market. History belongs to the indexer,
which reads the chain. /v1/tokens/search answered 501 on a TODO, a route lying
about the surface in the other direction. Gone, with a test that keeps them
gone.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 00:40:44 -07:00
zeekayandHanzo Dev f938357ff8 LLM.md: what the trading gateway needs before it is deployed anywhere
The three that cost time putting it behind api.lux.cloud — a single-arch image
in a mixed cluster, runAsNonRoot against a named user, and a health path that
answers 503 about a working gateway — plus why there is no admin surface.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 00:27:00 -07:00
zeekay e8610867ec feat(dex): update RPC routes to /v1/chain 2026-09-04 14:30:17 -07:00
zeekayandHanzo Dev 5a808d27f7 chain routes: /v1/bc -> /v1/chain
luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.

Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 08:21:06 -07:00
zeekayandHanzo Dev f56ed0a9c5 Restore the chain prefix the running nodes answer
The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.

The move belongs with the node redeploy, not ahead of it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 21:23:39 -07:00
zeekayandHanzo Dev 0abb4f609b chain routes: /v1/bc -> /v1/chain
luxd serves chain routes under /v1/chain; the /v1/bc prefix it replaced is
gone, so anything composing it reaches nothing. Lines that record an
observation - a probe, a status code, a past release - keep the prefix they
were written against, as do URLs on hosts that are not ours.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 20:55:26 -07:00
zeekayandHanzo Dev 93543e35b2 docs(llm): the v1.5 -> v1.14 D-Chain bump is a re-genesis, not an upgrade
The two lineages derive different genesis blocks. A fleet that keeps its v1.5
store keeps working, but any node whose store is wiped — or any validator added
later — adopts the v1.14 genesis, disagrees at height 0, and is stranded there
forever with isBootstrapped:false and no error naming the cause.

Found by wiping one devnet validator's D store after the roll: it came back on
genesis mpxP6xF3... while the fleet carried Muetn.... Wiping D on all five
converged them, and a subsequent wipe of one node then re-derived every block to
the fleet's exact root.

A matching height is not enough to verify this — every node reports height 0
either way. Compare lastAccepted.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:02:25 -07:00
zeekayandHanzo Dev a33fab670e docs(llm): a 404 on dex_* means old method names, not a dead D-Chain
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:42:41 -07:00
zeekayandHanzo Dev 9109a24081 dchain: the seam imported money and never placed an order
The C->D object carried value only, so executeImport credited the taker's
account and stopped. Its one reference to TxSubmit was a comment. Nothing placed
an order, so nothing crossed, so driveSeamExports — which fired only when a
non-input asset appeared — never fired, so Phase B had no object and the settle
reverted. The seam was unreachable by construction, which is why 0x9999 has
emitted zero events on every chain since genesis.

The object now carries the operation the taker authorized on C (market, side,
limit, size) appended to the unchanged 69-byte value head, and the drive builds
the taker's order from it: BuildBlock emits TxImport and TxIntentSubmit as a
pair, in one block, so value is never on D without an order against it.

TxIntentSubmit is the object-authorized sibling of TxSubmit, as TxImport is of
TxDeposit. It cannot be a plain TxSubmit — that type requires a signature at
parse, at the RPC edge, and in authorizeTx, and weakening any of those would let
an unsigned submit move any account's funds. It carries the intent id alone; the
zapwire Submit frame is derived from the escrow by seamSubmitBody, so the
operation is declared once and never restated for the two sides to reconcile.
From there it flows through the identical custody, matcher and settlement path a
signed dex_submit takes.

Three things this fixes on the way:

D could never sync past a settled swap. executeImport read the object from
shared memory during execute() while the consuming Remove landed at Accept, so
any node re-executing the block found it gone, derived a different ledger root
and died on `execution root mismatch`. The object's bytes now ride in the
TxImport body and Block.verifySeamImports authenticates them at Verify, where a
forgery costs the producer a block instead of forking a root. Accept now checks
the root it derives too: that branch is the bootstrap path and it was committing
whatever it computed while persisting the block's claimed root.

The seam ran in the taker's own ledger account, and the export drive settles an
account's balances back to C. A taker holding native D balance would have had
all of it exported, and C credits that out of the shared seam reserve — a raid
on other takers' pooled input. Each intent now runs in its own account, so the
question does not arise and the spent witness becomes exact rather than
inferred.

A zero-fill order left only its input behind, which the old export gate could
never match, so the principal sat on D while C's deadline reclaim refunded the
taker the same principal. The escrow now records that the order RAN — including
when the custody gate refused it — and the drive gates on that fact.

A market id named by the other chain reached bookForPool and aborted the block.
Any C caller could have halted D with it. Unknown market and unbound assets are
now per-tx rejects, and the intent settles back in full.

SeamPendingTrait rotates to .v2 alongside the wire. Old and new nodes cannot
disagree about a block: the TxImport body is 150 bytes and an old node's
bodySize says 32, so ParseBlock refuses the frame outright. It stalls rather
than forking. No historical block anywhere contains a TxImport.

121 tests pass in pkg/dchain, 0 fail; whole repo green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:13:43 -07:00
zeekayandHanzo Dev 20e62658cf docs(llm): record the Go toolchain policy
Two rules for every Go builder stage (pin latest stable patch; set
GOTOOLCHAIN=auto), and why the root `go` directive stays at 1.26.4. Notes
that sdk/lx-trading-go already declares go 1.26.5 — above the root module — so
the GOTOOLCHAIN=local failure mode is live in this repo, not hypothetical.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 16:17:46 -07:00
zeekayandHanzo Dev 8179bf6ff6 refactor(dex): finish lx→dex fold — fix doc refs (pkg/lx → pkg/dex)
Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 01:20:32 -07:00
zeekayandHanzo Dev 10a084c0b2 refactor(dex): fold pkg/lx into pkg/dex — one matcher, one package
The matching engine (pkg/lx, 45.5K LOC incl. tests) and the deterministic
core layered on it (pkg/dex) are one matcher spread across two packages
(dex imported lx in 12 files; lx imported neither). Fold lx into dex and
drop the "lx" name entirely — pkg/dex is now the single matcher package.

Pure rename/fold, ZERO logic change. Behavior proven identical: the merged
pkg/dex runs 457 top-level tests (431 former-lx + 26 former-dex) and 489
test funcs (468 + 21), all green — matching, batch (BatchMatchEqualsSequential,
run-to-run determinism, tile boundary), GPU/CPU parity, truncation, and CL
swap math all pass unchanged.

What moved:
- git mv all 101 pkg/lx/*.go + 1 .c into pkg/dex/ (package lx -> package dex).
- lx's swap_test.go -> swap_math_test.go (only filename collision with dex's
  existing swap_test.go).
- Rewrote imports repo-wide: 54 pkg/lx import lines removed; 42 external
  importers (api, client, consensus, dchain, dpdk, lxgpu, marketdata, test/,
  tests/) repointed to pkg/dex with lx.X -> dex.X; the 12 in-package dex files
  had lx.X -> bare X. 717 selector lines rewritten. Stale pkg/lx comment refs
  updated (pkg/lxgpu left intact).

Collisions resolved (2, both package-level vars):
- ErrNoLiquidity: lx's pool-level error (dexError, used only in swap.go, never
  referenced via qualifier) renamed ErrPoolNoLiquidity to match the existing
  ErrPool* convention; dex's router-level ErrNoLiquidity (public, has errors.Is
  tests) kept.
- priceMultiplierBig: identical after fold (both big.NewInt(PriceMultiplier));
  deduped by deleting dex's copy.

Not folded — pkg/orderbook: it is NOT the GPU seam (that is orderbook_gpu.go,
which lives in the engine and folded in here). pkg/orderbook is a zero-importer,
placeholder-cgo island whose 13 core domain identifiers (Order, OrderBook, Trade,
OrderType, OrderStatus, PriceLevel, Buy, Sell, Market, Limit, Filled,
PartiallyFilled, NewOrderBook) collide incompatibly with the engine's. Folding
it verbatim breaks the build; folding via rename reintroduces the exact parallel
duplication this fold removes. Left for owner decision (recommend deletion as
dead code) — see refactor notes.

Follow-on (separate repos, not touched): 19 files under luxfi/dex import
pkg/lx externally — lux/precompile/dex, lux/precompile-dex/dex,
lux/consensus/examples/01-simple-bridge, lux/_consensus-v12537/examples,
lux/cex/pkg/engine — repoint to pkg/dex after this lands.

Verified: CGO_ENABLED=0 GOWORK=off go build ./... clean; go vet ./... clean;
go test ./... -count=1 all green (22 ok / 0 fail).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 00:57:08 -07:00
zeekay c9daf10e22 fix(lx): delete fake benchmarks + false perf claims; use real measured numbers
Delete 3 AI-slop benchmarks from pkg/lx/critical_path_bench_test.go that measured no real work and reported fabricated throughput:
- BenchmarkMLXAcceleration: b.Skip + no-op slice index, then fabricated orders/sec
- BenchmarkQuantumSignatures (+verifyQuantumSignature): verify == len(sig)==512 && len(pub)==256
- BenchmarkConsensusFinalization (+hashBlock/verifyBlock): byte copy + len(hash)==32
Real benchmarks (CriticalOrderMatching, TradeExecution, RiskCheck, EndToEndLatency, ...) untouched; no imports left unused.

Replace fake '434,782,609 orders/sec (GPU/MLX) - 2ns/order - zero-alloc' claims in README.md and LLM.md (traced to archive/_lp108-2026-05-04/mlx_engine.go, a 'pure Go simulation' hardcoding OrdersPerSecond: 1000000.0) with numbers measured first-hand on Apple M1 Max:
- C++: 11.88M orders/s (10 threads), 169 ns avg match (p50 125 / p99 292), 5.91M single-thread, 49 ns cancel
- Go pkg/lx: 2.2M orders/s, 381 ns/order, 1-2 allocs/op (NOT zero-alloc)
- DEX matching stays on CPU; the real GPU win is FHE Metal NTT (23x speedup, N=4096 batch=128).
2026-06-26 12:55:14 -07:00
zeekay f544b3d071 chore: use Lux-native naming for the DEX core/EVM-adapter model
Reword comparison docs, comments, and design prose to Lux's own terms
(native DEX core, EVM adapter / 0x9999 V4 PoolManager, native HFT surface,
one consensus-finalized C+D transition). Rename the test broker-venue label
and oracle source weight to neutral names. Behavior-preserving: no logic,
ABI, or wire format changes.
2026-06-22 16:07:21 -07:00
zeekay bdd566d522 fix(lx): return match fills directly, not via stale trade-log re-slice
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.
2026-06-20 12:25:06 -07:00
zeekay f01b692cce scrub tenant branding + corona→corona (OSS brand hygiene) 2026-06-11 00:22:24 -07:00
zeekay d2334b5cdb fix: replace WL chain refs with Lux mainnet defaults 2026-03-30 19:18:24 -07:00
zeekay aa2519c830 docs: add Trading API to OpenAPI spec and LLM.md
- OpenAPI 3.0.3 spec now covers all 7 /v1/trade/* endpoints
  with full request/response schemas
- LLM.md documents pkg/trading architecture, venue interface,
  calldata encoding, chain IDs, and consumer usage pattern
2026-03-29 18:11:11 -07:00
zeekay 9ee0d3e876 chore: sync dependencies and format code 2026-02-04 15:50:40 -08:00
zeekay 4114fef626 feat: add gateway for DEX routing
- Add gateway package for multi-DEX routing
- Add Lux and Uniswap providers
- Add server and registry components
- Update dependencies
2025-12-28 11:00:04 -08:00
zeekay 7e722200a3 Expand on SDKs 2025-12-13 00:07:05 -08:00
zeekay fae0efeb1b chore: normalize naming to LX across codebase
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
2025-12-11 21:04:37 -08:00
zeekay 1aa4e9b3bb refactor: Q-Chain is verifier, not price source
Q-Chain provides quantum finality verification for prices/orders from
other sources, NOT its own price feed. Changes:

- Rename QChainSource to QChainVerifier (with backwards compat alias)
- Remove Q-Chain from price aggregator (not a Source interface)
- Q-Chain only used with -verify flag to verify finality
- Update architecture diagram to show Q-Chain as verification layer

Price Sources (6 total):
- X-Chain (2.0): Native DEX orderbook
- C-Chain (1.8): AMM pools
- Zoo Chain (1.7): Zoo Labs DEX
- A-Chain (1.5): Validator-attested network oracle
- Pyth (1.2): External oracle
- Chainlink (1.0): External oracle (reference)

Q-Chain Verifier:
- Post-quantum signatures (dilithium, sphincs+)
- Cross-chain order inclusion proofs
- Validator quorum consensus (3/4)
- ~50ms finality latency
2025-12-11 14:44:34 -08:00
zeekay e1ff2213e4 refactor: Adjust price source weights for proper layering
Weight hierarchy (DEX > AMM > Attestations > Oracles):
- X-Chain: 2.0 (native DEX orderbook - highest trust)
- C-Chain: 1.8 (on-chain AMM pools)
- Zoo Chain: 1.7 (Zoo Labs EVM DEX)
- Q-Chain: 1.6 (quantum finality verification)
- A-Chain: 1.5 (validator attestations)
- Pyth: 1.2 (external oracle)
- Chainlink: 1.0 (external oracle - reference only)

Also documented:
- Latency-adjusted weighting formula (planned)
- Future omni-DEX integrations (Uniswap, SushiSwap, Hyperliquid)
- Adaptive weight calculation based on measured latency
2025-12-11 14:32:10 -08:00
zeekay 439efe9f3d docs: Update LLM.md with comprehensive price feed architecture
- Document all 7 price sources with weights and intervals
- Add X-Chain, A-Chain, C-Chain, Q-Chain, Zoo Chain sources
- Document liquid token pairs (LZOO, LETH, LSOL, LBTC, etc.)
- Add Q-Chain quantum finality verification details
- Document post-quantum signature algorithms (dilithium, sphincs+)
- Add symbol normalization documentation
- Include price-feed CLI usage examples
- Update remaining work checklist
2025-12-11 14:30:28 -08:00
zeekay 1b4c54b3a8 feat: Add multi-source price aggregation for HFT trading
Add pkg/price package with pluggable price sources:
- OrderbookSource: Local orderbook mid-price (<100ns target)
- PythSource: Pyth Network WebSocket real-time feed
- ChainlinkSource: Chainlink oracle polling
- CChainSource: Lux C-Chain AMM pool prices

Oracle aggregator with:
- Weighted median price aggregation
- TWAP/VWAP calculations (5-minute windows)
- Circuit breakers for price validation
- Multi-source confidence scoring
- Outlier detection and filtering

Designed for co-located HFT clients requiring sub-millisecond
latency for cross-market arbitrage.

Also adds LLM.md project documentation with CLAUDE.md and
AGENTS.md symlinked for AI assistant context.
2025-12-11 11:54:54 -08:00