DEX V4 Receipt-Settlement Precompile (0x9999)
The canonical EVM-side entry point to the Lux DEX. A Uniswap-V4-PoolManager-shaped precompile at 0x9999 that does NOT match — it settles BLS-certified D-Chain fill receipts. The D-Chain (dexvm) matches and signs receipts; the C-Chain verifies the certificate inline (deterministic, fork-safe) and debits/credits EVM balances under Block-STM parallel execution. BLS is the day-1 certificate; Q/Quasar (and PQ/ZK) swap in later via a certType registry with no ABI change.
Implementation status (code-audited 2026-07-03): PARTIAL 0x9999 address, AlwaysOn module, DEXFill event and activation confirmed (precompile-dex/dex/settle_addr.go:16, settle_module.go:120-145, events.go:35, evm/precompile/registry/bridge.go:53); the SHIPPING design is the two-phase C↔D atomic seam — settle9999.go:16-40 states NO BLS certificate / NO DFillReceipt; the BLS DFillReceipt model lives only in build-excluded _deprecated_bls/.
Documentation: dex.lux.network
Source: github.com/luxfi/dex
Status
Draft. No backwards compatibility. No flags. No replay. The production DEX precompile address is 0x0000000000000000000000000000000000009999; the prior 0x9010 (LP-9010) is deprecated (see §10 Migration).
Abstract
0x9999 is the canonical EVM-side entry point to the Lux DEX. It exposes the
Uniswap-V4 PoolManager ABI unchanged, but it is not a matcher and not a
backend selector — it is a receipt-settlement adapter. The matching engine,
the resting book, fills, and value conservation live entirely on the D-Chain
(dexvm, LP-032), which a permissioned subset of
validators runs as a normal Lux chain. The D-Chain matches an order and a
D-validator quorum BLS-signs a DFillReceipt. The C-Chain 0x9999
precompile then verifies that certificate inline — BLS aggregate
verification is deterministic, so it is consensus-safe inside Run — and
debits/credits EVM balances. Many such settlements execute in parallel under
Block-STM (LP-1024) on the
cEVM.
The design slogan:
D matches · C settles · BLS certifies (day-1) · Q strengthens later · X finalizes value.
The app-developer model stays exactly Uniswap/CowSwap/1inch:
amountOut = PoolManager(0x9999).swap(key, params, hookData);
swap returns amountOut or reverts. A revert means nothing happened — no input
taken, no output credited, no partial DEX state.
This LP is the EVM settlement layer of the DEX. The core trading protocol is LP-9000; the matching engine is LP-9001; the matcher VM (D-Chain) is LP-032.
Motivation
A central-limit-order-book DEX cannot put every order through EVM consensus and
still scale. It also cannot let the EVM precompile call a live matcher during
block execution: a live clob_submit against a moving book is re-executed by
every validator at Verify at a different instant, producing different fills and
a different state root — a consensus fork (this exact scenario is forbidden by
the D-Chain's own red-team test TestRED_PerValidatorRelay_SplitsConsensus).
The resolution is a strict build-vs-verify split carried by a certificate:
- The build/propose path (a node that validates D) obtains the fill from the
local matcher and produces a signed
DFillReceipt. - The verify/replay path (every C-Chain validator) settles only from the certificate — never from a live matcher.
Because the certificate is verified deterministically, the C-Chain validator set need not run the matcher. With ~10 D-Chain validators and ~1000 C-Chain validators, all 1000 verify the same compact receipt and settle in parallel, while only the ~10 match. This is the scalable split.
Specification
1. Address and ABI
The precompile lives at 0x0000000000000000000000000000000000009999 and exposes
the Uniswap-V4 PoolManager ABI unchanged
(swap selector 0xF3CD914C, plus initialize 0x6276CBBE,
modifyLiquidity 0x5A6BCFDA, donate, deposit, withdraw, balanceOf,
extsload). A contract or wallet compiled against V4 runs unchanged; only the
deployment address is configured to 0x9999. The fill certificate rides in the
V4 hookData parameter (or, equivalently, the block-header predicate rail;
see §4), so the ABI itself is untouched. The precompile address is registered in
LP-9015.
0x9999 is the canonical settlement address and is identical to the
LXSettleAddress / DEXPoolManagerAddress constants — it is the money path.
The 0x999X address family. The DEX EVM surface occupies a fixed,
contiguous block. Only 0x9999 moves value; 0x9996 composes it; the views are
structurally read-only; the tail is reserved.
| Address | Name | Status | Writes value? |
|---|---|---|---|
0x9999 | DEXPoolManager / LXSettle | Live (always-on) | Yes — the sole settlement path |
0x9998 | DEXQuoter | Live | No (read-only view) |
0x9997 | DEXStateView | Live | No (read-only view) |
0x9996 | DEXPositionManager | Live | No — composes 0x9999 (CLOB position adapter) |
0x9995 | DEXPermit | Reserved (constant only) | — |
0x9994 | DEXFHE | Reserved (constant only) | — |
0x9993 | DEXAdmin | Reserved (constant only) | — |
The EVM sub-call allowlist that grants ERC-20 movement power is exactly
{0x9999, 0x9996}; the views can never write.
Activation gate (deterministic, genesis-preserving). A precompile that
injects account state at a fork must activate identically on every node, or the
state root diverges and the chain forks. The safe trigger is therefore a value
that is already part of the agreed-upon consensus record and is monotone: the
block header timestamp. 0x9999 is an always-on module gated on a single
protocol constant ActivationTime = 1766704800 (2025-12-25 23:20 UTC). The genesis-injection is a
pure function of consensus header timestamps: at the block whose
(parentTime < ActivationTime ≤ blockTime) — the genesis-or-crossing transition
— the state processor sets the 0x9999 account marker (nonce=1,
code=0x01); genesis passes parentTime=nil and installs the marker iff
ActivationTime ≤ genesisTime. Because the decision reads only the block and
parent header timestamps (consensus fields every node agrees on) and compile-time
constants — no wall-clock, RNG, env, or filesystem input — every validator
recomputes the identical activation on the verify/accept path. This is the same
mechanism Ethereum uses for timestamp forks (Shanghai/Cancun) and is
consensus-fork-safe by construction. Any chain whose genesis predates
ActivationTime reproduces its original pre-0x9999 genesis state byte for
byte (no marker, no activation nonce); a chain genesised at or after activation
carries the marker from block 0. This preserves the genesis hash of every
pre-2025 Lux chain while bringing 0x9999 online at the fork.
The DEXFill settlement event. On a successful settlement 0x9999 emits the
canonical, indexable native-CLOB fill signal:
event DEXFill(bytes32 indexed poolId, address indexed taker,
uint256 amountOut, uint256 blockNumber);
topic0 = keccak256("DEXFill(bytes32,address,uint256,uint256)")= 0x6f744d074efc4fa8512636853f8a4c67842f230d4279421c7599dc5c5daf9874topics[1] = poolId,topics[2] = taker,data = (amountOut, blockNumber).
DEXFill is the sole on-chain source a subgraph/indexer uses to materialise
native fills (LP-9027 wire → graph). It is scoped to 0x9999 and emitted only on
the Phase-B credit of a settled receipt, so a fill is indexable exactly once and
exactly when value moved.
2. Roles
| Chain | VM | Role |
|---|---|---|
| D-Chain | dexvm (LP-032) | Matches (CLOB, GPU/native, market-sharded). Emits DFillReceipt. A D-validator quorum BLS-signs the receipt root. Validated by a permissioned subset (the DEX validator set). |
| C-Chain | cEVM | Settles. 0x9999 verifies the receipt certificate, debits/credits EVM balances, marks the receipt consumed. Block-STM (LP-1024) parallelizes settlement. Never matches. |
| Q-Chain | Quasar | Later. Wraps D/C/X/Z roots, global ordering, PQ/finality cert profiles. Not required day-1. |
| X-Chain | — | Value finality / universal settlement where required. |
3. DFillReceiptV1
The receipt is strictly bound so it can settle exactly one swap, exactly once, on exactly the intended chain and precompile.
DFillReceiptV1 {
domain = "lux.dex.fill.v1"
networkID
dChainID
cChainID
dHeight
dBlockID
marketID
fillID
receiptID // unique; the replay key
poolKeyHash // == keccak(PoolKey)
swapParamsHash // == keccak(SwapParams)
hookCommitment // optional
sender // == msg.sender (or an authorized operator)
recipient
tokenInAssetID // full 32-byte AssetID, never an alias/fold
tokenOutAssetID
amountIn
amountOut
feeAmount
feeAssetID
deadline
nonce
precompileAddress = 0x9999
dStateRoot
bookRoot
fillRoot
accountDeltaRoot // optional
certType // see §5
cert // see §5
}
0x9999.swap verifies, in order, and reverts on any failure:
networkID,cChainID,precompileAddressequal the current chain/precompile.poolKeyHash == keccak(key)andswapParamsHash == keccak(params).sender == msg.sender(or a registered operator forsender).recipient == params.recipient;amountOut >= params.minAmountOut.deadline >= block.timestamp.receiptIDis not inconsumedReceipt.- The certificate is valid for the D-validator set active at
dHeight(§5). tokenInAssetID/tokenOutAssetIDare registered; the pool is active.- The DEX is not halted for the relevant scope (§7).
On success it debits tokenIn from sender, credits tokenOut to recipient,
writes consumedReceipt[receiptID], and returns the V4 BalanceDelta.
4. Build-vs-verify split (the safety invariant)
- Build/propose (a node that validates D,
--dex-validator): obtain the fill from the localdexvmand assemble + sign theDFillReceipt.D_CHAIN_UNAVAILABLEis returned only on this path when the node does not run D. - Verify/replay (every C validator, inside
Run): the precompile MUST NOT querydexvm, a venue, or any live matcher. It settles solely from the receipt certificate inhookData(or from the block-header predicate railGetPredicateResults(txHash, 0x9999), the warp-style carrier — deterministic, sorted, already part of the consensus header).
Certificate verification is a pure function of the receipt bytes and the verifier key, so it produces an identical result on every validator. This is what makes a single-transaction synchronous swap consensus-safe.
5. Certificate types and the verifier registry (day-1 BLS, Q later)
The certificate kind is a versioned enum, dispatched through an on-chain registry, so the cryptography can be upgraded with no ABI change:
certType: 1 = BLS_FAST_PATH // day-1
2 = Q_CERT // QuasarCert
3 = Q_CERT_PQ // PQ finality profile
4 = ZK_PROOF // STARK/zk batch proof
VerifierRegistry {
dChainID
certType
verifierKey
validatorSetID
quorumNumerator // registry-pinned quorum (governance authority);
quorumDenominator // NEVER cert-supplied — see BLSCert below
activationHeight
status
}
Day-1 BLS_FAST_PATH:
BLSCert {
version
certType
validatorSetID // selects the VerifierRegistry entry (pubkeys + quorum)
signerBitmap // bit i set => validator i (canonical order) signed
signedMessageHash // == signedMessage(...); C recomputes and equality-checks
aggregateSignature // BLS12-381 G2 compressed
}
The quorum fraction is not on the wire — a cert cannot lower its own
acceptance threshold (closing the "attacker sets quorum = 1/large -> single-signer
drain"). It is registry-pinned (VerifierRegistry.quorumNumerator/Denominator,
governance authority), and a hard absolute >=2/3 BFT floor is enforced on top
regardless, matching the Lux warp VerifyWeight convention
(signerWeight * 3 >= totalWeight * 2, 128-bit cross-multiply, no overflow).
signedMessage binds the full economic content of the fill — sender,
recipient, assets, and amounts — not merely an opaque receiptID a forger could
copy onto a different payload. Any tampered field yields a different message and
the cert no longer verifies:
signedMessage = SHA256("lux.dex.bls.fill.v1",
networkID, dChainID, cChainID, dHeight, dBlockID,
receiptRoot, dStateRoot, fillRoot,
marketID, fillID, poolKeyHash, swapParamsHash,
sender, recipient, tokenInAssetID, tokenOutAssetID,
amountIn, amountOut, feeAmount, feeAssetID,
deadline, nonce, precompileAddress, certType)
The D-Chain signer hashes the identical extended message over the identical fields; SHA-256 (not keccak) is the agreed attestation domain for the D<->C pair.
For efficiency the D-validator quorum signs a receipt root per D block/batch;
each C-Chain swap then carries { receipt, Merkle inclusion proof, one BLS cert over receiptRoot }. (Day-1 MAY begin with one cert per receipt for simplicity;
the batch-root form is the target.) Q/PQ/ZK certificates replace the BLS verifier
later by registering a new certType at an activationHeight; the V4 ABI and the
DFillReceipt envelope are unchanged. The full certType rollout
(BLS_FAST_PATH → Q_CERT → Q_CERT_PQ → ZK_PROOF) is governed by
LP-9022.
6. Block-STM execution (cEVM)
0x9999 is Block-STM-aware: it declares its read/write access set so the executor
runs non-conflicting settlements in parallel and only serializes genuine
conflicts.
swap access set:
R: dexHaltStatus, poolConfig[poolID],
assetRegistry[tokenIn], assetRegistry[tokenOut],
verifierRegistry[dChainID][validatorSetID],
consumedReceipt[receiptID],
balance[sender][tokenIn], balance[recipient][tokenOut],
allowance[sender][0x9999][tokenIn] // ERC-20 only
W: consumedReceipt[receiptID],
balance[sender][tokenIn], balance[recipient][tokenOut],
feeBucket[feeAssetID][epoch % N],
allowance[sender][0x9999][tokenIn] // ERC-20 only
Storage keys are fine-grained; there are no global hot write slots (no
global nonce, volume counter, single fee accumulator, single settlement counter,
or last-receipt slot). Analytics use sharded accumulators
(feeBucket[asset][epoch % N], volume[poolID][epochShard]). Two swaps conflict
only if they share an account, asset, receipt, allowance, or pool key — so
thousands of independent swaps settle in parallel. The cEVM read/write-set
conflict detector (LP-1024)
covers precompile writes.
7. Halt / safe stop
Trading is stoppable at both layers; halt state is replicated consensus state (not per-node), authorized by the protocol-fee controller / governance. The DEX halt modes integrate with the protocol-wide emergency system in LP-9016.
| Layer | Modes |
|---|---|
| D-Chain | HALT_NEW_ORDERS, HALT_MATCHING, CANCEL_ONLY, SETTLE_ONLY, FULL_HALT |
0x9999 | HALT_NEW_SWAPS, SETTLE_ONLY, DISABLE_RECEIPT_TYPE, DISABLE_ASSET, DISABLE_MARKET |
Halt keys are fine-grained: haltGlobal, haltMarket[id], haltAsset[id],
haltReceiptType[certType], haltValidatorSet[id] (haltGlobal is a read-only
hot key — reads do not contend; it is written rarely). The safe default on
incident is: stop new swaps; allow cancel, settle of already-certified safe
receipts, and withdrawals/refunds. A halted swap reverts cleanly with no
partial state. The precompile retains the pre-existing
pauseDEX/resumeDEX/pausePool/freezePool selectors, gated on the protocol-fee
controller.
8. FHE and execution modes
FHE is not in the vanilla swap hot path. A receipt carries an
executionMode ∈ { PUBLIC_CLOB, FHE_CLOB, Z_PRIVATE }; the public CLOB is the
fastest path. Private/encrypted routes use the same receipt-settlement
framework when enabled. Day-1, FHE is disabled or shadow-mode for the DEX so
first-launch latency is governed by the public path alone.
9. Security invariants
- Full 32-byte
AccountIDandAssetIDeverywhere — never a matcher handle, alias, or fold. CustodyOpIDbinds the C-Chain tx hash and call index.- No declared-asset override; no fallback identity; no zero-lock executable orders.
- ERC-20: day-1 an allowlist of strict tokens (no rebasing / fee-on-transfer /
callback), observed-delta accounting,
nonReentrant; arbitrary tokens are wrapped to canonicalAssetIDbalances first. - All-or-nothing settlement (ownership invariant, not only conservation: only the named counterparties move value).
- Replay protection via
consumedReceipt[receiptID]. - Restart replays carried receipts identically.
10. Migration from 0x9010
0x9999 is the sole production money path. 0x9010
(LP-9010) is deprecated and MUST be one of:
(a) disabled for value-moving calls (revert PRECOMPILE_MOVED), or (b) a
forwarder that calls the exact same implementation and the same
consumedReceipt / halt / verifier-registry storage namespace
(dex.precompile.v1.9999.*). There MUST NOT be two active money paths or two
separate consumed-receipt maps. Web and mobile clients change only the configured
address (0x9010 → 0x9999); the V4 call site is unchanged.
Rationale
- Receipt-settlement over live relay is the only model that keeps a single-tx synchronous swap consensus-safe while the matcher is a separate chain: the cert is verified deterministically, the matcher is never called at Verify.
- BLS first, Q later delivers the smooth day-1 trading UX without blocking on
the full Quasar finality stack; the
certTyperegistry makes the upgrade an on-chain configuration change, not an ABI break. - Block-STM with fine-grained keys turns the universal C-Chain validator set into a parallel settlement fabric, which is the throughput story for day-1.
- V4 ABI preserved means the entire Uniswap-tooling and the existing Lux exchange web/mobile clients work with only an address change.
Backwards Compatibility
None by intent. 0x9010 is retired to a single, namespace-shared path (§10). No
flags, no dual money paths.
Reference Implementation
~/work/lux/precompile/dex— the0x9999adapter.~/work/lux/chains/dexvm— the D-Chain matcher andDFillReceiptproduction (LP-032).~/work/lux/evm— cEVM / Block-STM settlement.~/work/lux/node— the DEX validator set and local chain resolution on the build path.
Benchmark matrix and day-1 latency targets (D p50 match < 1 ms/shard, C BLS root verified once per batch, E2E p50 within a few fast blocks) are maintained in LP-9020 and the implementation's benchmark suite.
Security Considerations
The fork-safety of the EVM path rests entirely on §4: the verify/replay path must
never call a live matcher and must settle only from a deterministically verified
certificate. Any change that reintroduces a live dexvm/venue call inside Run
on the verify path reintroduces the consensus fork. The D-validator quorum and
the verifier registry are the trust root for settlement; their key rotation and
activationHeight discipline are consensus-critical. Halt authority is a
privileged capability (protocol-fee controller / governance) and MUST default to
fund-preserving modes.
Related LPs
- LP-9000: DEX Core Specification
- LP-9001: DEX Trading Engine (matching)
- LP-9010: DEX Precompile (superseded by this LP)
- LP-9015: Precompile Registry (0x9999 entry)
- LP-9016: Emergency Procedures (DEX halt)
- LP-9020: Performance Benchmarks
- LP-9022: Upgrade Procedures (certType path)
- LP-1024: Parallel Validation / Block-STM
- LP-032: DEX VM (the D-Chain matcher)
- LP-3520: Precompile Suite Overview
Copyright
Copyright and related rights waived via CC0. </content> </invoke>