166 Commits
Author SHA1 Message Date
zeekayandHanzo Dev b5a79f7e49 chain letters are lowercase
The primary-network chains answer to either case; one spelling beats two.
/v1/chain/c/rpc, /v1/chain/p, /v1/chain/x across the estate.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 15:24:21 -07:00
zeekayandHanzo Dev 84587ddb0f Resolve the market before a swap can name a halt
SettleSwap took the caller's PoolKey and never asked whether that pool
existed, so key.ID() fed the halt slot, the volume shard and the fill
event alike. A fabricated key names a slot nobody set, and the swap
settles while the real market is halted.

Resolving once above the phase switch and passing the resolved poolID
down means no phase -- including one added later -- can key on anything
the registry has not admitted. checkHalt no longer accepts a key, so it
cannot derive one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 02:41:14 -07:00
zeekay 30327bb7ac docs: chain routes are /v1/chain
luxd serves chain APIs under one segment from constants.ChainAliasPrefix
(constants v1.6.4, node v1.36.181):

  /v1/bc/<alias>/...   -> /v1/chain/<alias>/...
  /ext/bc/<alias>/...  -> /v1/chain/<alias>/...

Observations keep their original addresses: a dated note or a recorded status
code is a fact about when it was written, not an instruction to follow.
2026-08-30 17:17:26 -07:00
zeekay 9d7d771465 Cite specs that exist, at their current numbers
LP-4835 now specifies 0x012220, so the STARK-FRI slot is allocated
rather than a placeholder, and 0x012205 next door verifies the P3Q
threshold-signature family. The 0x999x constants were labelled with LP
numbers that were never issued; only LP-9999 exists, and its address
table specifies the family.
2026-08-28 20:29:37 -07:00
zeekay 5ab320e270 dex, fhe: finish the refactor the merge half-applied
collectPosition's calldata is a fixed 156 bytes now — claimID, asset,
positionID, and the 60-byte claim — so the two tests that built the old
128-byte layout were refusing on length before reaching what they meant to
exercise. The reentrancy test carries a real claim; the amount-domain test
becomes a width sweep, because there is no longer an amount word to put out of
range: the amount is the uint64 inside the claim, which a caller cannot widen.
ErrLPCollectBadAmount goes with it, having become a sentinel nothing returns.

The swap arm's write witness moves 13 -> 10. One custody pot and no per-order
escrow is less state, and the witness exists to make that a decision rather
than a drift.

fhe's derived gas comments still stated the products of the old wall-clock
figures — 180M and 936M against constants that now compute 300M and 2.88G.
2026-08-28 20:19:27 -07:00
zeekay 1362566d34 Merge github/main into the hardening branch
Both sides carried work. Ours is the security pass: the stableswap amp=0
and empty-pool halts, the wei-per-swap leak, uint256 truncation in math,
three verifiers that returned a verdict without verifying, the cgo/non-cgo
consensus split in cggmp21, and the starkfri soundness floor. Theirs is
the fhe parameter set, the mldsa crafted-length refusal, and the naming
sweep.

Resolved per hunk rather than by side. Where both fixed one defect the
better fix survives alone, never two guards for one bug.
2026-08-28 20:13:25 -07:00
zeekay d4474142f1 dex, e2e, LLM.md: pin the hook and market surfaces, and point e2e at current deps
The D-Chain, hook and market surfaces had behaviour no test observed. e2e/clob
moves to the module versions this repo builds against. LLM.md records the two
orthogonal calldata bounds — Cursor for how many bytes, Unsigned/Signed for
which values — and why hand-writing either is the bug they exist to close.
2026-08-28 15:39:28 -07:00
zeekay fe4430cec6 dex, fhe, contract: a 256-bit word narrowed to a machine integer without saying so
big.Int.Uint64 and .Int64 are documented "if x cannot be represented, the result
is undefined". On calldata that is not a lossy conversion to reason about, it is
a value substitution, and the substitution happens twice in series: Uint64 drops
everything above bit 63, and the Go conversion that follows it -- uint24(x),
which is uint32(x) because uint24 is an alias -- drops everything above bit 31.
A reader who checks only the first concludes the site is safe.

contract.Unsigned and contract.Signed refuse a word that does not fit its
declared width, once, where the bytes become a value. Six production sites use
them; nothing downstream has to remember which of its arguments were bounded.

DecodePoolKey read fee and tickSpacing as 24-bit fields and enforced neither.
Above 2^32 the narrowing substituted: a tickSpacing word of 2^32+6 arrived as 6,
and 2^32+60 produced the same pool id as 60. Between 2^24 and 2^32 the field held
the caller's value faithfully while EncodePoolKeyABI wrote only its low three
bytes, so Fee 3000 and Fee 2^24+3000 hash to the SAME id -- PoolKey.ID is not
injective over its own Go type, and the decoder handed back a key whose fields
are not the ones its id was derived from. The encoder is unchanged; changing it
renames every pool that exists. What changed is that no such key can be built
from calldata, which empties the collision class at the only boundary that
produces PoolKeys from untrusted bytes. Same bound on the two tick fields in
DecodeModifyLiquidityInput and decodePMLifecycle.

Three engines narrowed INSIDE the comparison meant to bound the value, so the
check did not merely miss, it inverted. A margin removal to 2^64x leverage read
as leverage zero and passed; a perp open at 2^64 in hundredths did the same;
increasePosition wrapped the quotient into a uint32 field that then set the
liquidation price. Leverage is now compared at full width. CalculateFee clamped
AFTER narrowing, so a violent enough move came out cheaper than a calm one; it
clamps in 256 bits and narrows once the clamp proves it fits. GetTimeToRepayment
saturates rather than wrapping a horizon into a small number of blocks.

fhe declares euint128/160/256 and loads them through EncryptUint64, whose bit
loop reads value>>i off a uint64 -- zero for every i above 63. The container is
genuinely wider (tfheRandom fills all 256 bits), so the missing bits were not
rounded away, they were encrypted AS ZERO: measured, asEuint256(2^200) decrypted
to 0, and asEaddress on two addresses sharing their last eight bytes produced the
same plaintext, so a homomorphic equality over encrypted addresses answered true
for two different accounts. Casts to a narrower type still truncate, matching
Solidity and the existing asEuint4/8/16/32 masks; what is refused is a plaintext
wider than the loader, which is not a cast but a value the encryptor cannot
carry. Widening it needs an EncryptBig upstream -- BitCiphertext's fields are
unexported and it has no constructor from bits. Same bound on the scalar ops,
once ahead of the switch rather than five times inside it.

Consensus-visible: words that decoded now refuse. Fee or tickSpacing wider than
24 bits, a tick wider than int24, an FHE plaintext or scalar wider than 64 bits.
Every one of them previously resolved to a different value than the caller sent.

Four prior lanes had pinned three of these as characterisation tests naming
themselves DEFECT, each instructing that the test be inverted when the bound
arrived; they are inverted here rather than deleted, so the history of why stays
legible. 24 of 24 mutations caught, no survivors. contract 100.0%, dex 99.1 ->
99.2. Both CGO settings, -count=1, -count=2 and -race.
2026-08-28 15:08:29 -07:00
zeekay fa8eb6aaea dex: record which surfaces ask whether a market was ever registered
initialize is where a market becomes real: it resolves both assets through the
installed resolver and requires live on-chain code at each address. The LP
commit, the quoter and both stateview reads re-read that record and refuse an
inactive one. The swap money path does not read it at all, so a Phase-A intent
against a key initialize never saw is accepted -- it locks the caller's asset
into the seam and exports a cross-chain object carrying a market id nothing
admitted. The lock is the caller's own and remains reclaimable, so this is
permissionless state growth rather than theft, but the admission performed at
initialize governs nothing the value path does.

Closing it is the same one-line status check the LP commit already makes.
Landing it changes which calls a chain accepts, and it fails eighteen existing
tests -- every one of which swaps against a key it never registered -- so it is a
coordinated upgrade and a decision above this change, not a patch to slip in.

What lands here is the measurement: one test for the predicate, one for the
surfaces that ask it, and one that pins the swap path answering yes today. That
last one fails the moment the check is added, and says so in its own failure
message, so the gap cannot be closed silently or forgotten quietly.
2026-08-28 14:21:08 -07:00
zeekay a79ed1beee dex: an exact-output quote answers the exact-output question
quoteExactOutput and quoteExactOutputSingle discarded their direction into `_`
and returned the exact-input projection, so a caller asking what 1000 token1
costs got what 1000 token0 buys -- wrong by the square of the price, with the
fee taken off the wrong side. At price 4 it answered 4000 where the answer is
250. The two questions are inverses of one price-and-fee map: the exact-output
side divides where the other multiplies and grosses the fee onto the input
rather than deducting it. Every step rounds up, so the quoted input covers the
output asked for and no smaller input does -- swept over five fees, five prices,
both directions and six amounts, with the one-unit-lower input asserted short.
The exact-input answer is unchanged, which is asserted directly.

A token move refuses an address carrying no contract code. The EVM answers a
CALL into such an address with success and no return data, and that is also how
a non-compliant ERC-20 reports success, so the return alone cannot separate
them; SafeERC20 separates them by requiring code, and now so does this. It
matters on the push leg: withdraw debits the claim before it moves the token, so
reading that empty return as delivery retires a claim against a transfer that
did not happen. The pull leg was already covered by its delivered-amount check.
One moveToken now issues both, so the requirement and the return reading cannot
drift apart. Mutation: dropping the code check fails two tests. A token that
HAS code and returns nothing still settles, which is asserted separately -- the
refusal is bought by the code check alone.

getOpenOrders charges for the record loop before running it. The order count
comes from the index alone, so the bill is knowable first; the sibling
enumeration on 0x9996 already charged at that point.

A length read out of a storage word is bounded before it sizes an allocation.
int(uint64) is negative above 2^63 and make takes that as a panic, which from a
precompile is a halt rather than a refusal. slotLen is the one bound and the
three readers -- record, order index, staging -- answer absent above it. This
replaces a characterisation test that asserted the panic and asked to be
rewritten once the bound existed.

reclaim no longer stages a remove it cannot deliver. A remove names a key in the
inbound space, where objects a peer exported to this chain live; the intent
object went the other way, so the staged remove could never name it, and against
a missing key it writes a tombstone into a namespace that has nothing to do with
it. What reclaim guarantees on this chain -- the principal exits once, because
the zeroed remaining caps any later settlement -- is unchanged and now stated
where the comment claimed more.
2026-08-28 14:14:21 -07:00
zeekay 2b0e35ad84 dex: a deposit may claim only what a deposit delivered
0x9999 holds ONE native balance backing THREE pots -- settleVault (depositor
claims), seamReserve (swap-rail input locks) and committedPositions (LP
commits). A precompile cannot read msg.value, so every native funding path asks
the same question by difference: balance(0x9999) minus what is already
accounted for. The operator seed subtracted all three pots. deposit subtracted
settleVault alone, so it read the seam and LP pots as value the caller had just
carried and minted a claim over them; withdraw then paid that claim out of the
one balance, leaving the intents and positions that locked it unbacked. Its own
comment stated the property the code did not hold.

deliveredNative is now that question, asked in one place and answered the same
way for both paths. A pot added later cannot be forgotten by half the callers,
because there is only one caller of the arithmetic. Mutation: dropping the two
pot terms fails ten tests, four new and six that already pinned the seed side.

The five Run entry points read their selector through contract.Cursor. Three of
them spelled the same four-byte read three different ways, and all five handed
their handlers input[4:] -- a two-index slice, so cap ran on into the EVM memory
the caller filled with MSTORE before the call. Every downstream guard bounds its
own high index correctly today, but the property was held up by fifty
independent hand-written checks; Cursor.Rest caps the slice to its own length,
so a slice expression past the declared input refuses instead of returning
bytes the caller chose. Suite green with the cap in place, which is the evidence
that no live path reads past its input.
2026-08-28 14:00:28 -07:00
zeekay 5ec478c3ed stableswap, math: two halts from calldata, and a pool that leaked a wei per swap
A precompile panic aborts the block rather than the call, so both of these
stopped every validator on any chain with stableswap enabled.

An amplification coefficient of zero divides by zero in all three Newton ops --
computeY's c/(A*n*n) and D/(A*n), and computeD's denominator (A*n-1)*D +
(n+1)*dP, which crosses exactly zero. A 40,000-input sweep hit it 665 times. A
is now read and refused at the boundary, which is also the honest answer: the
invariant is undefined at A=0.

addLiquidity divides by D0, and computeD returns zero for an empty pool.
Balances and totalSupply arrive as independent calldata, so "no assets, LP
tokens outstanding" is reachable. Post-fix sweep: zero panics in 40,000 inputs.

computeY used floor division, so its iterates drifted BELOW the true root in
55% of solves, by up to 2 wei, which getDy's single -1 margin did not cover.
The invariant therefore DECREASED across a swap -- 27 losses in 19,996 random
swaps, a wei at a time out of the pool. Newton converges from above on this
convex function, so ceiling division restores the one-sidedness the proof
depends on. After: zero losses, 19,847 gains.

math returned every result through padTo32, which keeps only the low 32 bytes.
MulDiv(2^255, 4, 1) returned 0. MulDiv(max, max, 1) returned 1. And dex.MulDiv
PANICS on the same input -- two behaviours for one operation, one of them a
plausible-looking wrong number in the primitive that computes fees and share
ratios. Now refuses with ErrOverflow, matching FullMath.sol. Consensus-visible;
it needs a coordinated upgrade.

Coverage: dex 57.1 -> 99.1, stableswap 80.6 -> 98.6, swap 87.6 -> 99.6,
v3 78.1 -> 98.7, math 78.5 -> 97.6. 50 of 52 mutations caught, both survivors
proven unreachable rather than assumed. dex is now order-independent: -count=2
and -shuffle=on both pass, 843 tests run individually and 260 shuffle seeds
clean. The leak was a package-level resolver latch that installed on top of
whatever was live; t.Cleanup had fixed the exit and not the entry.

Also corrected five comments describing the CLOB price limit as IEEE-754
float64 bits. It is exact big.Int on an integer grid, and a reviewer would have
believed consensus compared float bit patterns.
2026-08-28 13:46:40 -07:00
zeekay 33a2acfbd3 dex: pin what margin, perps, teleport and vaults actually do
Four files at 0% coverage, now 99.6 / 100 / 95.1 / 100. Tests only; no source
line changed. 51 of 52 mutations caught, the survivor proven equivalent rather
than assumed: at profit == 0 the fall-through computes a zero fee, a zero net,
a no-op add and returns a fresh zero, which is byte-identical to the early
return no test can separate.

The reachability verdict comes first, because it decides how to read the rest.
Nothing dispatches to any of it. MarginEngine, TeleportBridge, OmnichainRouter,
VaultManager and PerpetualEngine have zero references outside their own files,
the five registered modules reach none of them, and all four are pure in-memory
Go with no StateDB anywhere -- so they cannot take part in a state transition
even if something did call them. Every defect below is latent.

Two of them are the same shape that produced three universal forgeries in zk
this week: a length check returned as a verdict. verifyWarpMessage is
`return len(message) > 0`, so any non-empty byte string authorises a mint, and
it never looks at the teleport id it is handed. Alongside it, bridge signatures
are counted and never examined -- three copies of one byte satisfy a threshold
of three.

Seven public entry points panic on arithmetic: division by a zero leverage,
zero size, zero margin, zero index price, zero total assets, and a
CreateVault that slices a strategy name to ten bytes without checking its
length -- the engine's own LP_YIELD is eight, so creating a vault over it is an
unconditional panic. RouteTransfer takes the write lock and then calls
GetBestRoute, which takes the read lock on the same non-reentrant mutex, so
every routed transfer deadlocks permanently.

The margin constants are all ten times their documented values, which is one
root cause with three consequences: above 20x a long's liquidation price sits
above its entry, the long/short ordering inverts, and fees at 750bps exceed
maintenance at 500bps -- so the full-fee liquidation branch is dead code and
every liquidation is a socialised loss.

Also latent: a daily transfer limit stored and never enforced, admin actions
with no caller argument at all and an operator list nothing reads, a fee floor
that can exceed the amount transferred, an over-close that pays PnL on phantom
size, a withdrawal solvency check that restates what the caller already proved,
and a leverage ceiling bypassed by a uint64 truncation.

Three uncovered blocks, each provably unreachable and named in the report: one
is unreachable precisely because the check guarding it is vacuous.
2026-08-28 11:48:47 -07:00
zeekay 68fd104380 dex: the package builds again
Three compile errors left by interrupted work. Two tests named CurrencyIn and
CurrencyOut on SwapExactInputSingleParams, whose fields are TokenIn and
TokenOut; one wrote 2^96 as an untyped literal to big.NewInt, which takes an
int64 and cannot hold it -- built with Lsh instead.

Also lands comment corrections that had fallen behind the code: the CLOB price
limit is an exact uint64 on the PriceInt grid, not float64 bits, and no float
touches the price path any more. Consensus code that reads a float is a
divergence waiting for a different CPU, so the doc saying otherwise was worth
fixing.

The package now builds and vets clean. It does not pass: the suite has an
order dependence that ends in a division by zero inside GetAccountHealth, which
in a precompile is an unrecovered panic. Both are being worked separately.
2026-08-28 11:38:50 -07:00
zeekayandHanzo Dev 8407af3039 dex: the call index in a claim id was never observed by a test
A C->D claim id is SHA-256(domain | source | dest | sourceTx | index), and the
index is the precompile invocation's position within the transaction. It is what
makes the derivation injective for a transaction that crosses more than once — a
contract that commits two positions, or commits and swaps, in one frame.

Every harness ran at call index 0 and never moved it, so nothing distinguished
CallIndex() from the literal 0. Replacing the call with a constant passes the
entire suite. It should not: both crossings then derive the same claim id, the
export replay guard fires on the second, and the transaction reverts. A live
rail would refuse every multi-crossing transaction while the tests stayed green.

Two crossings in one transaction, differing only in call index, must get two
distinct accepted claims; repeating an index inside a transaction must be
refused as a replay rather than overwrite a staged claim. The mutant dies on the
first.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:48:28 -07:00
zeekayandHanzo Dev b8d2f08b9e dex: retire creditPositionFee — it reflected fees the D-Chain does not compute
native_state.go opens by defining the pot:

    custody[a] = operator seed of a
               + sum of value exported to D
               - sum of value imported from D

One pot per asset is defensible exactly while that holds, because one pot means
every claim draws on the same number, and the identity is what makes that number
"value C holds because D owns it" — no more, so a claim cannot draw on backing
nobody on D is owed, and no less, so every D holder can be paid.

creditPositionFee was a fourth term. Governance delivered real value and it
raised custody, an LP record's withdrawable, and that owner's reserve, as "the
keeper's per-owner reflection of fees the D-Chain CLOB credited a maker".

The D-Chain credits no maker fees. Its settlement path has no fee engine and
says so in as many words (dex pkg/dchain/settle.go: "deliberately does NOT model
a full fee/tick/lot/minNotional engine"). The function reflected an event that
does not occur.

It could not have worked if it did. An LP collects only by consuming a D->C
claim, and D exports only what a D account holds; value credited on C never
reached D, so nothing on D could export it. The record promised a withdrawable
the rail had no way to deliver, and the real value landed in the shared pot
where it backed everyone's claims instead of that LP's. Its own test had to
hand-write a D->C object for principal+fees — value D never held. The
fabrication was the feature.

It had no ABI encoder, no keeper, and no caller anywhere outside its own tests.
It was the only writer of that fourth term and the only raiser of LockedAmt
outside a real commit. It was also the only position-moving path with no halt
gate, and it needed no atomic capability, so unlike the rest of the rail it was
live on every network today.

Deleted. The identity now holds as written and seedSeamReserve is the only way
value enters custody other than a C->D export. When D grows fees they will be
D-side balance in the maker's own account and will leave by the ordinary export,
like every other unit — the way out already exists, and it is the only one.

The two LP tests that ran a fee credit now assert what is actually true of them:
a collect is bounded by custody, and a commit plus a full collect is
value-neutral.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:46:47 -07:00
zeekayandHanzo Dev 566a8d89e9 dex: the two version stamps did not move when the object they version did
The cross-chain object shrank from the braided 118-byte form to the 60-byte
funding claim. Both stamps that exist to mark exactly that event stayed where
they were.

The staged-op record's leading version byte is still 1, and the comment above it
says an unknown version must fail the flush rather than silently mis-decode
value. Today the old record is refused, but by WIDTH — 183 bytes where 125 are
expected — which is a coincidence, not a statement. Two layouts that happen to
agree on width decode into each other. Bump it to 2.

This does NOT rescue a v0.19.8 staged Put; that record was fatal before and is
fatal now, and it should be. What changes is that the refusal is a version
mismatch instead of a width mismatch. Nothing has ever flushed a staged Put on
any live network, so the set of records this migrates is empty — which is the
only moment the stamp can be corrected at no cost.

The object commitment's domain is still ...import.object.v1 across the same
change. A declaration is a hash plus a promise about what was hashed, and the
domain is where that promise lives; leaving it behind lets a commitment from one
era be presented as a commitment from another. The previous object change
rotated it for this reason. Rotate it to v2.

Also fixes the collectPosition calldata comment, which still specified
object[69] with the braided field list, three widths behind the code beneath it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:34:50 -07:00
zeekayandHanzo Dev d544607475 dex: a collect took whose-record from the calldata, so it took it from the attacker
collectPosition binds the claim to the caller — Import checks the object's
recorded beneficiary, so the value can only ever pay the party it names. The
positionID rode in beside it and was checked against nothing.

The record it names is somebody's property. A stranger holding a legitimate
claim of their own, for any amount, could name any LP's live record and the
bookkeeping would faithfully apply their collect to it. A collect that reaches
the record's withdrawable marks it Cancelled, which is terminal: the LP's value
is still on D, the record that brings it back is closed, and
requestPositionWithdraw refuses it as already closed. The only way out is
committing new money.

The comment above the function explained why it is not a gate — the record
follows the value, custody backs the credit, the claim is consumable once — and
that is all true and all about the VALUE. Whose record the value lands in is a
different question, and nothing was answering it.

So the owner is a parameter. There is no way to call it without saying whose
record this is, and a live record belonging to someone else is refused rather
than silently mutated. A record that is absent or already fully collected still
stays where it is: that tolerance was deliberate and is untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:32:02 -07:00
zeekayandHanzo Dev e0a77cceaf dex: the comments described a seam that no longer exists
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:44:38 -07:00
zeekayandHanzo Dev 0b433110ac dex: the funding rail carries value, and nothing else
Move once, trade many. Value crosses the C<->D boundary twice in its life; in
between, D owns it and every order, match and trade is a local balance movement.
So the crossing has exactly one job — move ownership exactly once — and the
object shrinks to what that job needs:

    beneficiary(20) | asset(32) | amount(8)   = 60 bytes

Out of the wire: the 49-byte market/side/limit/size tail, the `spent` execution
witness, and the lane byte selecting a trading pot. Out of the precompile: the
per-order escrow with its principal cap and deadline, the taker-authenticated
price floor, the deadline reclaim and its selector, the shared-memory discovery
trait, and the .v2 domain rotation that existed only because the tail had
changed the object's width.

ONE CUSTODY POT. seamReserve and committedPositions were the swap lane and the
LP lane of one quantity — "value C holds because D owns it" — and the lane byte
was the only thing keeping them apart. Splitting them is what created the
cross-lane drain the byte then had to defend against. One number per asset, one
credit path, nothing to cross.

WHAT REPLACES THE PER-ORDER CAP AND THE PRICE FLOOR: D refuses an over-debit on
export, C refuses a credit custody cannot back, a claim is consumable exactly
once, and Block.Verify proves the carried bytes are the recorded object. Those
are properties of the value. The cap and the floor were properties of an order,
which is why the order had to ride the object for them to be checkable at all.

The claim id is one derivation for both directions and both repos:
SHA-256(domain | source | dest | sourceTx | index). It replaces DeriveOrderID,
DerivePositionCommitID and deriveSeamOutputID, which were three spellings of the
same fact.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:33:14 -07:00
zeekayandHanzo Dev 42d2759b52 wip: drop order/slippage code the funding rail no longer reaches
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:25:28 -07:00
zeekayandHanzo Dev f8ca4a37be wip: C-side funding rail un-braid (build green)
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:23:58 -07:00
zeekayandHanzo Dev 2391e31aaa dex: the seam's unit of work is an order, not an intent
Order -> Match -> Execution -> Trade is the vocabulary; "intent" was the old
name for what an Order is. Renames the identifiers across the 0x9999 settle
precompile so the code reads in the settled terms.

Values that live on a chain keep their bytes, because renaming one of those is
a migration and not a rename:

  - "lux.dex.native.intent.v1" and "lux.dex.native.intent.pending.v2" are hash
    INPUTS; a byte changed there moves every derived order id and the pending
    trait both repos must agree on.
  - settleStateNamespace + "intent" is an on-disk key prefix; renaming it
    orphans the submitted-order replay set.
  - reclaimIntent(bytes32), IntentSubmitted(...) and IntentReclaimed(...) are
    the deployed ABI; the signature is what hashes, so a rename would move a
    selector and two topics.

Each of those now carries one line saying why the literal does not track the
identifier that names it, so the inconsistency is not "fixed" later.

TestOrderID_CrossRepoGolden still reads 5b0e8d38... and
TestSeamPendingTrait_MatchesDChainGolden still reads 5f10f9ad..., which is the
evidence no derivation input moved.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 16:13:01 -07:00
zeekayandHanzo Dev 5eef0272c9 docs(dex): say plainly that the C->D wire change forks the C-Chain
The object's bytes are staged into StateDB, so they are part of the C state
root: an old node stages 69 bytes across 4 storage words where this one stages
118 across 6, and the zero-price-limit refusal turns a success into a revert.
Two C nodes across that line executing the same swap derive different roots.

That is not a caveat to discover during a rollout. Upgrade every C and D
validator first; the seam is inert until both sides are up, because the two
discovery traits are disjoint.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:40:28 -07:00
zeekayandHanzo Dev c5843595b3 docs(dex): the staged-record comments still described a 60-byte object
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:21:00 -07:00
zeekayandHanzo Dev 56e332bff2 dex: the C->D object carries the taker's order, not just their money
The 69-byte C->D object was a VALUE: this much of this asset belongs to this
owner. A value says what moves; it never said what to do with it. So D consumed
the intent, credited the taker's account, and stopped. Nothing placed an order,
nothing crossed, no proceeds existed, the settlement export never fired, Phase B
had no object, and the settle reverted. eth_getLogs on 0x9999 returns zero events
on every chain since genesis because the seam was unreachable by construction,
not because it was misconfigured.

A swap intent now appends the operation the taker authorized:

    market(32) | side(1) | limitPrice(8) | size(8)

onto the unchanged value head, giving a 118-byte intent object. Composed, not
braided: the head is byte-for-byte what every settlement and LP consumer already
reads, so their golden vectors stay valid, and width alone discriminates the two
readings. 69 is a value object and instructs nothing; 118 is an instruction. A
69-byte object is refused as an intent rather than imported with an invented
market and side, which would be an order the taker never signed.

The limit is required. A cross-chain taker cannot re-price mid-flight and their
order runs in a block they do not control, so an unbounded market order across
the seam is precisely the sandwich the spent witness exists to refuse — and at a
zero limit that check is vacuous. Refused at submission, before any principal
moves, rather than sent out naked. Size is derived from the taker's own limit
(exact integer division on the PriceInt grid), so the lock can never exceed what
was locked.

The discovery trait rotates to .v2. That is the flag day, and it is what keeps
the two generations from ever disagreeing: an old D node enumerates .v1, finds no
v2 object, emits no import, and goes on validating everything else; a new D node
enumerates .v2 and never touches a stale op-less object. Neither can execute the
other's intent, so the changeover costs seam liveness alone.

isMarket and limitIsUpper are gone from the wire and from IntentRequest — every
seam order is a limit order, and a BUY's limit is its ceiling. One declaration of
each fact instead of two to reconcile.

344 tests pass, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:12:40 -07:00
zeekayandHanzo Dev fd1cc0904e dex: carry the cross-chain object in the transaction, not shared memory
A settled swap could never be synced past. Both C-side consume paths
(ImportSettlement, ImportPositionCollect) read the D->C object from shared
memory DURING EVM execution, while the consuming Remove landed at block
Accept. A node re-executing that block — bootstrapping, state-syncing,
re-tracing — found the object gone, computed a reverted receipt where the
network had computed a successful one, and died on the receipt root. The
seam settled live and wedged every node that tried to follow.

Move the object's 69 bytes into the transaction (Phase-B hookData, and the
collectPosition calldata) so execution is a pure function of the block and
replays byte-identically forever. Both rails now go through ONE binding
primitive, bindImportObject, which also DECLARES the consumption as a log
committing to the object's hash. The host authenticates that declaration
against shared memory at Block.Verify, so a forged object costs the
producer a block instead of forking a receipt root.

The ship rule did not weaken, it moved: a C balance is still credited only
by consuming a real D->C object. What changed is where that is proven.

Also:
- amount is no longer restated on the wire; it IS the object's amount, so
  there is one declaration of the fact instead of two to cross-check.
- the retired DS01 tag is refused BY NAME. The fallback for an unknown tag
  is Phase A, so a stale settle would otherwise have been silently re-read
  as an intent and LOCKED the sender's input — the one mixed-fleet outcome
  worse than a revert.
- the zero-amount refusal moved into the single binding point, so an absent
  object zero-padded to canonical width reports the same clear error on
  both rails rather than surfacing as a cross-rail violation.

336 tests pass, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 06:40:23 -07:00
zeekayandHanzo Dev 8c2cba6f6f dex: tag C->D swap intents with the discovery trait D enumerates by
A Phase-A intent was written to shared memory carrying only an owner trait.
That trait cannot be enumerated: atomic.SharedMemory.Indexed returns keys
possessing a trait the CALLER names, and the D-Chain does not know the owner
set in advance. So a real 0x9999 intent was invisible to the D-Chain's
autonomous import drive, which enumerates pending intents by a fixed,
owner-agnostic tag inside BuildBlock.

This is a second, independent reason the seam is dark, and it survives
fixing the transport: with shared memory reachable but no discovery trait,
every intent would land in shared memory and sit there unmatched forever.
The D side has carried this constant, and a comment naming this exact
companion change, since it was written; the C side never made it.

Swap rail only. The LP rail's lifecycle is driven by its position record,
not by enumeration, and tagging it would inject LP objects into the swap
drive's queue.

The value is pinned by golden vector against dex/pkg/dchain's constant, the
same discipline the 69-byte object wire already uses, because the two repos
cannot import each other. Drift would be silent in the worst way — D
querying a trait C never writes enumerates nothing, logs nothing, and is
indistinguishable from "nobody is trading". Verified equal by executing
both sides: 6d4307f9...48f2.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 14:24:02 -07:00
zeekayandHanzo Dev c9b030cee9 docs: publish /v1/bc/C/rpc, drop dead /ext/ URLs
/ext/ is gone from api.lux.network, api.zoo.network and api.hanzo.network
(all 404 today), so every published /ext/ URL was already broken. Convert
to the one canonical path and fix the URL forms that were dead for other
reasons at the same time:

  /mainnet|/testnet|/devnet path prefix -> the per-network host (404 today)
  api.testnet.lux.network, testnet-api.lux.network -> api.lux-test.network
  /ext/C/rpc (missing bc/) -> /v1/bc/C/rpc
  chain aliases + blockchain IDs under api.lux.network -> the L1s own host

Third-party RPC hosts (Avalanche, Flare, blastapi) are left untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 18:00:29 -07:00
zeekay e9049b6dc4 chore: sync working tree
Commits 2 outstanding change(s) that were sitting uncommitted.
No build artifacts and no secrets in the changeset (both checked).
2026-07-26 10:03:00 -07:00
zeekayandHanzo Dev af68fed375 dex: decomplect to canonical settle-only 0x9999 (remove embedded matcher)
ONE money path: D-Chain matches, C-Chain 0x9999 SETTLES, C never matches.

- 0x9999 swap() routes ONLY to SettleSwap (native C<->D atomic seam): untagged/
  DI01 => Phase A intent (lock input, write C->D object, return intent id); DS01
  => Phase B settlement (consume a real D->C atomic object once, credit RECORDED
  amount). No-receipt-no-settle: absent a real object, Phase B reverts
  ErrNativeNoSettlement (TestDecomplect_NoReceiptNoSettle).
- Deleted the embedded matcher: swap_sync*.go, swap_custody.go (maker order book),
  buildRouter/evmAMMPool in swap_amm_pool.go, and the swapDeposit/withdraw/place/
  cancel selectors. A live in-trie matcher forks consensus; deterministic
  atomic-object consumption is fork-safe inline.
- 0x9010 already non-dispatched. 0x9012 (LXRouter) value selectors exactInput*/
  exactOutput* now revert ErrPrecompileMoved (PRECOMPILE_MOVED); read-only quote/
  route views kept.
- Cleanups (dead code removal, all unreachable via any dispatched selector):
  deleted the legacy engine PoolManager.Swap + LXRouter.Exact*/executeV4Swap*;
  deleted the vestigial makerLockedVault ledger (0 callers, always 0); deleted the
  unwired Block-STM scaffolding (AssertWriteSetWithin/AccessCommitment/Predict*).
- Activation rides the EXISTING canonical gate 1766708400 (Dec 25 2025 16:20 PST);
  no new timestamp. Build CGO_ENABLED=0 (pure-Go luxfi/dex path); go.sum zap
  re-tag checksum refreshed.

327 dex tests PASS / 0 FAIL, go vet clean. RED-reviewed (0 critical, all drain
vectors safe, fund-strand cleared, negative invariant falsified-then-restored).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-04 12:52:44 -07:00
zeekayandHanzo Dev 844f3d3587 dex: decomplect to canonical settle-only 0x9999 (remove embedded matcher)
ONE money path: D-Chain matches, C-Chain 0x9999 SETTLES, C never matches.

- 0x9999 swap() routes ONLY to SettleSwap (native C<->D atomic seam): untagged/
  DI01 => Phase A intent (lock input, write C->D object, return intent id); DS01
  => Phase B settlement (consume a real D->C atomic object once, credit RECORDED
  amount). No-receipt-no-settle: absent a real object, Phase B reverts
  ErrNativeNoSettlement (TestDecomplect_NoReceiptNoSettle).
- Deleted the embedded matcher: swap_sync*.go, swap_custody.go (maker order book),
  buildRouter/evmAMMPool in swap_amm_pool.go, and the swapDeposit/withdraw/place/
  cancel selectors. A live in-trie matcher forks consensus; deterministic
  atomic-object consumption is fork-safe inline.
- 0x9010 already non-dispatched. 0x9012 (LXRouter) value selectors exactInput*/
  exactOutput* now revert ErrPrecompileMoved (PRECOMPILE_MOVED); read-only quote/
  route views kept.
- Cleanups (dead code removal, all unreachable via any dispatched selector):
  deleted the legacy engine PoolManager.Swap + LXRouter.Exact*/executeV4Swap*;
  deleted the vestigial makerLockedVault ledger (0 callers, always 0); deleted the
  unwired Block-STM scaffolding (AssertWriteSetWithin/AccessCommitment/Predict*).
- Activation rides the EXISTING canonical gate 1766708400 (Dec 25 2025 16:20 PST);
  no new timestamp. Build CGO_ENABLED=0 (pure-Go luxfi/dex path); go.sum zap
  re-tag checksum refreshed.

327 dex tests PASS / 0 FAIL, go vet clean. RED-reviewed (0 critical, all drain
vectors safe, fund-strand cleared, negative invariant falsified-then-restored).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-04 12:52:44 -07:00
zeekayandHanzo Dev d30014b419 fix(dex/9999): exact-integer ZAP price wire + fold dexcore/lx -> pkg/dex
Bump github.com/luxfi/dex v1.5.20 -> v1.14.0. v1.14.0 (a) folded pkg/lx and
pkg/dexcore into a single pkg/dex, and (b) froze the ZAP wire to EXACT
INTEGERS: price is uint64 fixed-point ×1e8 (the PriceInt grid), no float64.
The 0x9999 EVM surface must speak that same grid or it prices a limit the
native venue reads at a different scale — an interop break.

Package fold (mechanical, behavior-neutral — build + full suite green after):
- import path pkg/dexcore -> pkg/dex (alias dexcore), pkg/lx -> pkg/dex.
- v1.14.0 renames: NewCLOBSource->NewOrderBookSource, SourceCLOB->SourceOrderBook,
  ClassPublicCLOB->ClassPublicDEX.
- collapsed the 4 files that imported the fold under both aliases to a single
  dexcore alias (no redundant same-package import).

Exact-integer price wire (the 7 float-wire sites):
- settle9999.go priceLimitToCLOB: return the CLOB limit as a uint64 FIXED-POINT
  ×priceScale integer computed by EXACT big.Int (round-to-nearest of
  s^2·priceScale/2^192), not math.Float64bits of a float. Round-to-nearest (not
  floor) so a sqrt-derived "50" ceiling still crosses a maker resting AT 50.
- swap_sync.go: limitPrice = lx.PriceInt(limitUnits) directly — no
  PriceToInt(Float64frombits(...)) roundtrip.
- native_dchain_client.go enforceProceedsPriceFloor: the MEV floor compares the
  integer limit (limit = priceLimit/priceScale) by big.Int cross-multiply
  (out·priceScale vs priceLimit·spent), dropping math.Float64frombits +
  big.Rat.SetFloat64. Determinism + no 2^53 truncation preserved.
- new package const priceScale (== dexcore.PriceMultiplier == zapwire.PriceScale);
  the intent/relay PriceLimit is now this fixed-point grid value.

swap_custody.go's runSwapPlace keeps its derived float for dexcore.PlaceOrder —
that is the matcher's in-process crossing-loop API (unchanged signature), a
projection, never a consensus/settlement value.

Tests: MEV-floor / price-limit / exact-precision (>2^53) redteam tests
translated to the fixed-point integer domain without weakening any invariant.
New wire_parity_test.go pins priceScale == dexcore.PriceMultiplier ==
zapwire.PriceScale so the grid can never drift from the venue. 437 pass, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:56:00 -07:00
zeekayandHanzo Dev 88714ea7c0 fix(dex/9999): exact-integer ZAP price wire + fold dexcore/lx -> pkg/dex
Bump github.com/luxfi/dex v1.5.20 -> v1.14.0. v1.14.0 (a) folded pkg/lx and
pkg/dexcore into a single pkg/dex, and (b) froze the ZAP wire to EXACT
INTEGERS: price is uint64 fixed-point ×1e8 (the PriceInt grid), no float64.
The 0x9999 EVM surface must speak that same grid or it prices a limit the
native venue reads at a different scale — an interop break.

Package fold (mechanical, behavior-neutral — build + full suite green after):
- import path pkg/dexcore -> pkg/dex (alias dexcore), pkg/lx -> pkg/dex.
- v1.14.0 renames: NewCLOBSource->NewOrderBookSource, SourceCLOB->SourceOrderBook,
  ClassPublicCLOB->ClassPublicDEX.
- collapsed the 4 files that imported the fold under both aliases to a single
  dexcore alias (no redundant same-package import).

Exact-integer price wire (the 7 float-wire sites):
- settle9999.go priceLimitToCLOB: return the CLOB limit as a uint64 FIXED-POINT
  ×priceScale integer computed by EXACT big.Int (round-to-nearest of
  s^2·priceScale/2^192), not math.Float64bits of a float. Round-to-nearest (not
  floor) so a sqrt-derived "50" ceiling still crosses a maker resting AT 50.
- swap_sync.go: limitPrice = lx.PriceInt(limitUnits) directly — no
  PriceToInt(Float64frombits(...)) roundtrip.
- native_dchain_client.go enforceProceedsPriceFloor: the MEV floor compares the
  integer limit (limit = priceLimit/priceScale) by big.Int cross-multiply
  (out·priceScale vs priceLimit·spent), dropping math.Float64frombits +
  big.Rat.SetFloat64. Determinism + no 2^53 truncation preserved.
- new package const priceScale (== dexcore.PriceMultiplier == zapwire.PriceScale);
  the intent/relay PriceLimit is now this fixed-point grid value.

swap_custody.go's runSwapPlace keeps its derived float for dexcore.PlaceOrder —
that is the matcher's in-process crossing-loop API (unchanged signature), a
projection, never a consensus/settlement value.

Tests: MEV-floor / price-limit / exact-precision (>2^53) redteam tests
translated to the fixed-point integer domain without weakening any invariant.
New wire_parity_test.go pins priceScale == dexcore.PriceMultiplier ==
zapwire.PriceScale so the grid can never drift from the venue. 437 pass, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:56:00 -07:00
zeekay 15e0635d04 fix(dex): exact-integer settlement price floor — kill float64 >2^53 truncation on the 0x9999 money path
enforceProceedsPriceFloor reconstructed the realized fill price in float64
(float64(spent), float64(out)) before comparing against the taker's recorded
MEV floor. spent/out are uint256-domain token amounts that routinely exceed
2^53, so the conversion silently dropped their low bits: a fill strictly BELOW
the taker's floor compared EQUAL and was ACCEPTED — the exact sandwich the
floor exists to refuse. (Proven: float64(2^53+1)==float64(2^53), old SELL floor
returns no-reject where exact integer math rejects.)

Replace with exact arithmetic: the recorded limit is taken as its lossless
rational value (big.Rat.SetFloat64) and cross-multiplied with the amounts as
big.Ints. Every term is an exact integer, so the verdict is byte-identical on
every validator (no float on the money path) and never truncates a uint256.

Regression: TestPriceFloor_Exact_Above2Pow53 (the flip case, both sides),
TestPriceFloor_MatchesExactReferenceSweep (cross-check vs independent big.Rat
reference across the 2^53 boundary), TestPriceFloor_EdgeCasesPreserved.
2026-06-27 21:23:06 -07:00
zeekay c928fc49eb fix(dex): exact-integer settlement price floor — kill float64 >2^53 truncation on the 0x9999 money path
enforceProceedsPriceFloor reconstructed the realized fill price in float64
(float64(spent), float64(out)) before comparing against the taker's recorded
MEV floor. spent/out are uint256-domain token amounts that routinely exceed
2^53, so the conversion silently dropped their low bits: a fill strictly BELOW
the taker's floor compared EQUAL and was ACCEPTED — the exact sandwich the
floor exists to refuse. (Proven: float64(2^53+1)==float64(2^53), old SELL floor
returns no-reject where exact integer math rejects.)

Replace with exact arithmetic: the recorded limit is taken as its lossless
rational value (big.Rat.SetFloat64) and cross-multiplied with the amounts as
big.Ints. Every term is an exact integer, so the verdict is byte-identical on
every validator (no float on the money path) and never truncates a uint256.

Regression: TestPriceFloor_Exact_Above2Pow53 (the flip case, both sides),
TestPriceFloor_MatchesExactReferenceSweep (cross-check vs independent big.Rat
reference across the 2^53 boundary), TestPriceFloor_EdgeCasesPreserved.
2026-06-27 21:23:06 -07:00
zeekay 8437c31119 dex: retire dormant LP-311 conduit + dead _deprecated_bls — ship only the real active money path
Forward-only, active-only: the native-ZAP path (Phase A/B ordering + non-empty-vault
marker) is THE one real 0x9999 settlement path that produces fills (proven over real
geth StateDB: deposit→place→swap→withdraw, DEXFill emitted, conservation holds). The
LP-311 conduit was built but never dispatched (dormant) and the BLS bundle was
deprecated — both removed. No dormant gated paths, no dead code, no backwards-compat.
2026-06-27 18:47:06 -07:00
zeekay 0ea7324b68 dex: retire dormant LP-311 conduit + dead _deprecated_bls — ship only the real active money path
Forward-only, active-only: the native-ZAP path (Phase A/B ordering + non-empty-vault
marker) is THE one real 0x9999 settlement path that produces fills (proven over real
geth StateDB: deposit→place→swap→withdraw, DEXFill emitted, conservation holds). The
LP-311 conduit was built but never dispatched (dormant) and the BLS bundle was
deprecated — both removed. No dormant gated paths, no dead code, no backwards-compat.
2026-06-27 18:47:06 -07:00
zeekay bd66095726 dex: LP-311 stateless 0x9999 conduit + host AtomicStager seam + real-StateDB reap-noop proof
Decomplect step 1 (LP-311 §5, the gate to live). The 0x9999 settlement conduit
drifted into a SECOND venue: it kept an order book + a per-asset/per-account ledger
+ atomic-staging records in its OWN account storage. Because 0x9999 is AlwaysOn and
dispatched by address, an ERC-20-only deposit leaves it EMPTY (nonce==0 ∧ balance==0
∧ codeHash==∅; storage is NOT part of EIP-158 empty()), so geth Finalise(true) reaps
the account AND its storage -> seamReserve stranded, zero fill. The shipped "fix" was
a nonce marker on 0x9999 (preserves the complect).

This adds the LP-311 STATELESS CONDUIT that dissolves the hazard at its cause:

- contract/atomic_stager.go: AtomicStager optional host capability (StageExport/
  ConsumeImport). The conduit moves cross-chain value objects through the HOST's
  non-empty staging account, never its own (reapable) account.

- dex/conduit.go: conduitForwardCredit / conduitApplyRelease. Escrow is native
  balance or the TOKEN's trie; the ledger + book live on the D-Chain venue; cross-
  domain objects are staged via AtomicStager. ZERO durable SetState(0x9999). The
  nonce marker is unnecessary and unused on this path. Reuses the proven C<->D object
  codec (native_wire.go), DeriveIntentID, and the raw escrow helpers (native balance /
  pullERC20Terminal under-delivery guard).

- dex/conduit_reap_test.go: the INVERSION of native_zap_realstate_test.go over a REAL
  *state.StateDB (EIP-158 on) + REAL cross-chain atomic.SharedMemory + a minimal-but-
  real D-Chain ledger venue. Drives ERC-20 deposit -> D-credit -> withdraw and proves:
  0x9999 holds ZERO own storage, is reaped at Finalise(true) as a NO-OP, escrow (token
  trie) + claim (D ledger) intact + withdrawable, conservation holds (no mint/loss).
  Plus the crisp RED/GREEN discriminator: same record reaped on empty 0x9999, survives
  on the non-empty host account.

Tests: TestConduit_OwnStorageReapIsNoOp_RealStateDB,
TestConduit_ReapDiscriminator_HostAccountVsConduit — GREEN (GOWORK=off CGO_ENABLED=0);
full dex suite green (no regressions). SYNC carry-fills no-fork primitive verified
green in chains/dexvm (TestRED_PerValidatorRelay_SplitsConsensus: identical roots, 0
venue calls at Verify).

Forward-only, patch-bump cascade. Old braided money path retained pending cutover
(repoint dispatch + delete C-side ledger -> D, embedded-venue deletion).
2026-06-27 16:59:13 -07:00
zeekay 6ea3143281 dex: LP-311 stateless 0x9999 conduit + host AtomicStager seam + real-StateDB reap-noop proof
Decomplect step 1 (LP-311 §5, the gate to live). The 0x9999 settlement conduit
drifted into a SECOND venue: it kept an order book + a per-asset/per-account ledger
+ atomic-staging records in its OWN account storage. Because 0x9999 is AlwaysOn and
dispatched by address, an ERC-20-only deposit leaves it EMPTY (nonce==0 ∧ balance==0
∧ codeHash==∅; storage is NOT part of EIP-158 empty()), so geth Finalise(true) reaps
the account AND its storage -> seamReserve stranded, zero fill. The shipped "fix" was
a nonce marker on 0x9999 (preserves the complect).

This adds the LP-311 STATELESS CONDUIT that dissolves the hazard at its cause:

- contract/atomic_stager.go: AtomicStager optional host capability (StageExport/
  ConsumeImport). The conduit moves cross-chain value objects through the HOST's
  non-empty staging account, never its own (reapable) account.

- dex/conduit.go: conduitForwardCredit / conduitApplyRelease. Escrow is native
  balance or the TOKEN's trie; the ledger + book live on the D-Chain venue; cross-
  domain objects are staged via AtomicStager. ZERO durable SetState(0x9999). The
  nonce marker is unnecessary and unused on this path. Reuses the proven C<->D object
  codec (native_wire.go), DeriveIntentID, and the raw escrow helpers (native balance /
  pullERC20Terminal under-delivery guard).

- dex/conduit_reap_test.go: the INVERSION of native_zap_realstate_test.go over a REAL
  *state.StateDB (EIP-158 on) + REAL cross-chain atomic.SharedMemory + a minimal-but-
  real D-Chain ledger venue. Drives ERC-20 deposit -> D-credit -> withdraw and proves:
  0x9999 holds ZERO own storage, is reaped at Finalise(true) as a NO-OP, escrow (token
  trie) + claim (D ledger) intact + withdrawable, conservation holds (no mint/loss).
  Plus the crisp RED/GREEN discriminator: same record reaped on empty 0x9999, survives
  on the non-empty host account.

Tests: TestConduit_OwnStorageReapIsNoOp_RealStateDB,
TestConduit_ReapDiscriminator_HostAccountVsConduit — GREEN (GOWORK=off CGO_ENABLED=0);
full dex suite green (no regressions). SYNC carry-fills no-fork primitive verified
green in chains/dexvm (TestRED_PerValidatorRelay_SplitsConsensus: identical roots, 0
venue calls at Verify).

Forward-only, patch-bump cascade. Old braided money path retained pending cutover
(repoint dispatch + delete C-side ledger -> D, embedded-venue deletion).
2026-06-27 16:59:13 -07:00
zeekay 1e33ee32ba decomplect: retire pqcrypto bundle (0x9003) — standalone mldsa/mlkem/slhdsa is the one way
Network reboot, no backward compat: delete the AI-slop pqcrypto bundle + every
gate pinning it (pqcryptoConfig genesis activation, relaunch-safety test pin).
ML-KEM stays via standalone mlkem (0x012201). pqcryptoConfig removed from all
upgrade.json/yaml; 0x9003 retired, never reuse.
2026-06-27 16:08:48 -07:00
zeekay 2bb0f91a13 decomplect: retire pqcrypto bundle (0x9003) — standalone mldsa/mlkem/slhdsa is the one way
Network reboot, no backward compat: delete the AI-slop pqcrypto bundle + every
gate pinning it (pqcryptoConfig genesis activation, relaunch-safety test pin).
ML-KEM stays via standalone mlkem (0x012201). pqcryptoConfig removed from all
upgrade.json/yaml; 0x9003 retired, never reuse.
2026-06-27 16:08:48 -07:00
zeekay 0803aa5966 precompile: native Uniswap-V3 concentrated-liquidity (0x90A1) + V2 router wire
V3 precompile composes the existing (untested) dex tick/sqrtPrice/swap math —
TickMath, SqrtPriceMath, SwapMath, liquidity — ABI-compatible with Uniswap V3
(initialize/mint/burn/collect/swap + views). 19/19 tests vs v3-core vectors
(getSqrtRatioAtTick(0)=2^96, ComputeSwapStep, e2e mint→swap→burn→collect w/
conservation). Router quoteV2 resolves to the native constant-product AMM.
Native, deterministic (big.Int, no float/GPU — runs in consensus).
2026-06-27 12:42:59 -07:00
zeekay 264637a90d precompile: native Uniswap-V3 concentrated-liquidity (0x90A1) + V2 router wire
V3 precompile composes the existing (untested) dex tick/sqrtPrice/swap math —
TickMath, SqrtPriceMath, SwapMath, liquidity — ABI-compatible with Uniswap V3
(initialize/mint/burn/collect/swap + views). 19/19 tests vs v3-core vectors
(getSqrtRatioAtTick(0)=2^96, ComputeSwapStep, e2e mint→swap→burn→collect w/
conservation). Router quoteV2 resolves to the native constant-product AMM.
Native, deterministic (big.Int, no float/GPU — runs in consensus).
2026-06-27 12:42:59 -07:00
zeekay 2f24b18e7e refactor(precompileconfig)!: warp v1.21.0 decomplect — AddMessage takes *warp.Core
Track the warp ZAP rename (SignedCore->Core, WarpEnvelope->Envelope).
WarpMessageWriter.AddMessage now takes *warp.Core. Doc comment rewritten to
drop the deleted RLP UnsignedMessage reference — one and one way only.
2026-06-26 22:23:21 -07:00
zeekay deb53869b3 refactor(precompileconfig)!: warp v1.21.0 decomplect — AddMessage takes *warp.Core
Track the warp ZAP rename (SignedCore->Core, WarpEnvelope->Envelope).
WarpMessageWriter.AddMessage now takes *warp.Core. Doc comment rewritten to
drop the deleted RLP UnsignedMessage reference — one and one way only.
2026-06-26 22:23:21 -07:00
zeekay c4e6bd0f07 feat(dex): 0x9999 settlement logs emit unconditionally (genesis-active, no dated fork)
Mirror of luxfi/evm: first-run network, no legacy — delete the DexSettleActivationTime
gate mirror + dexLogsActive predicate; settlement/swap/market logs emit unconditionally;
drop the now-dead blockTimestamp param from syncSwap. One way, no activation config.
2026-06-26 14:40:01 -07:00
zeekay 7b1c88c378 feat(dex): 0x9999 settlement logs emit unconditionally (genesis-active, no dated fork)
Mirror of luxfi/evm: first-run network, no legacy — delete the DexSettleActivationTime
gate mirror + dexLogsActive predicate; settlement/swap/market logs emit unconditionally;
drop the now-dead blockTimestamp param from syncSwap. One way, no activation config.
2026-06-26 14:40:01 -07:00
zeekay 2c8f4d700c deps: bump chains v1.3.18 + consensus v1.25.21 + crypto v1.19.22 (no stragglers); doc: clarify inStateVault live-bridge case 2026-06-23 09:26:07 -07:00
zeekay 9c4485bf23 deps: bump chains v1.3.18 + consensus v1.25.21 + crypto v1.19.22 (no stragglers); doc: clarify inStateVault live-bridge case 2026-06-23 09:26:07 -07:00