100 Commits
Author SHA1 Message Date
hanzo-dev 1a8f237eae ci: run linux jobs on lux-build-amd64 ARC scale set (no GitHub-hosted builders) 2026-07-11 00:16:58 -07:00
92312c1413 refactor(genesis): re-export LightMnemonic from published luxfi/light v1.0.0 (#14)
genesis.LightMnemonic becomes a backward-compat re-export of
light.Mnemonic (github.com/luxfi/light v1.0.0) — one source of truth for
the public dev seed, no local replace directive, published semver.

Orthogonal to the pre-existing constants v1.6.1 <-> ids MChainID/FChainID
skew that blocks this module's full build (verified: ids resolves to
v1.2.10 with and without this change; identical errors either way).

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-09 23:54:51 -07:00
Hanzo AI 76e9d86b6e chore: update 2026-06-10 13:40:17 -07:00
Hanzo AI 0abb293d3f chore: update 2026-06-10 13:34:21 -07:00
Hanzo AI fb53885497 go: 1.26.3 → 1.26.4 (security: crypto/x509, mime, net/textproto) 2026-06-06 22:09:18 -07:00
Hanzo AI c629280b97 deps: bump luxfi/vm v1.2.0, sampler v1.1.0, staking v1.5.0 (relicense)
Picks up the 2026-06-06 relicensing:
- luxfi/vm v1.2.0 → Lux Ecosystem License v1.2 (was Lux Research v1.0).
  License-only retag of the extracted runtime; patent reservation
  preserved for runtime optimization surfaces, royalty-free for
  Descending Chains.
- luxfi/sampler v1.1.0 → BSD-3-Clause (was Lux Research v1.0).
  License restored to match the luxfi/node provenance (originally
  extracted from node/utils/sampler).
- luxfi/staking v1.5.0 → BSD-3-Clause (was Lux Research v1.0).
  License restored to match the luxfi/node provenance.

No code changes in this commit — go.mod/go.sum only.
2026-06-06 19:02:28 -07:00
Hanzo AI 9e7a67de8e terminology: brand-l1-*.json → l2-*.json + 'brand L1' → 'L2' in inventory 2026-06-06 13:44:25 -07:00
Hanzo AI e2f88fb594 go.sum: drop stale accel versions (Wave 2G-Cascade) 2026-06-06 06:35:55 -07:00
Hanzo AI d5bf6e461b deps: bump p2p v1.21.1, node v1.29.3, sdk v1.17.6, vm v1.1.11 (Wave 2G-Cascade)
cmd: also drop the local codec replace directive — codec v1.1.5 has the
canonical zapcodec extracted to its own module per Wave 2G-Archive.
2026-06-06 06:31:34 -07:00
Hanzo AI bba8e11fb4 genesis/builder: migrate from staging shim to proto/zap_codec
Wave 2G-Archive completes the genesis-builder migration: the staging
shim builder/zap_codec.go (created during Wave 2G-Genesis to mirror
proto/zap_codec's surface before it was tagged) is now deleted, and
builder.go imports proto/zap_codec directly.

Changes:
  - builder.go: pvmGenesisCodec / newXVMParserCodecs swap to
    proto/zap_codec.NewPVMGenesis / NewXVMParser
  - builder/zap_codec.go: deleted (staging shim no longer needed)
  - go.mod: bump luxfi/proto v1.1.0 → v1.3.0 (first tag that exposes
    proto/zap_codec; v1.3.0 also includes the proto/internal test
    helper migration from Wave 2G-Internal)

luxfi/codec demoted to // indirect — kept transitively via deps that
haven't re-tagged yet. Will drop on the next upstream tagging cascade.
2026-06-06 05:52:54 -07:00
Hanzo AI 8da1cca53b genesis/builder: ZAP-native genesis codec (Wave 2G-Genesis)
Migrates pvmGenesisCodec() and newXVMParserCodecs() off the legacy
linearcodec big-endian wire format onto the ZAP-native zapcodec
little-endian wire format. Aligned with LP-023 ZAP-native activation
(proto/zap_native: ZAPActivationUnix=0 — "ZAP mandatory from genesis"),
forward-only.

Wire-format verdict: BREAK.

ZAP-native bytes are NOT byte-equivalent to legacy linearcodec bytes —
endianness flips for every uint16/uint32/uint64/string-length prefix.
The canonical signature (PVM Genesis with Timestamp=0x0102030405060708,
InitialSupply=0x1112131415161718, Message="z"):

  legacy linearcodec (BE): ... 01 02 03 04 05 06 07 08  11 12 13 14 15 16 17 18 ...
  ZAP-native zapcodec (LE): ... 08 07 06 05 04 03 02 01  18 17 16 15 14 13 12 11 ...

No coordinated network upgrade needed: this is the first ZAP-native
genesis tag, and no production validator was running legacy linearcodec
in the post-LP-023 fleet. Mainnet/testnet/devnet/localnet all bootstrap
ZAP-only from genesis.

Code changes:

  - builder.go:
      Drop github.com/luxfi/codec + .../linearcodec direct imports.
      Drop math.MaxInt32 inline construction.
      pvmGenesisCodec()    calls newZapCodecPVMGenesis(CodecVersion)
                           + pchainblock.RegisterTypes(cm).
      newXVMParserCodecs() calls newZapCodecXVMParser(CodecVersion).
      Mirrors sdk/wallet/chain/p/pcodecs.NewPVMGenesisCodec and
      sdk/wallet/chain/x/constants.go::newXVMParserCodecs which already
      moved to proto/zap_codec in Wave 2G-Wallet.

  - zap_codec.go (new):
      Staging shim mirroring proto/zap_codec's public surface
      (NewPVMGenesis, NewXVMParser, Manager.{Marshal,Unmarshal,Size,
      RegisterType,SkipRegistrations}). When Wave 2G-Wallet lands and
      tags github.com/luxfi/proto/zap_codec, this file deletes and the
      two helpers in builder.go swap to that import — mechanical, no
      behavioural change because both shim and target wrap the same
      zapcodec.Codec backend.

  - wire_test.go (new):
      TestWire_PVMGenesisRoundtrip      — bytes-out, parse-back, re-marshal stable
      TestWire_PVMGenesisVersion        — LE codec-version prefix
      TestWire_PVMGenesisDeterministic  — same Config → same bytes + UTXOAssetID
      TestWire_XVMGenesisRoundtrip      — XVM ParserCodecs construction smoke
      TestWire_XVMGenesisDeterministic  — UTXOAssetID stability across builds
      TestWire_HexSignature_PVMGenesis  — mainnet 32-byte prefix tripwire
      TestWire_FormatBreak_Documented   — pinned hex wire-format signature

  - go.mod:
      luxfi/codec  v1.1.4 (indirect) → v1.1.5 (direct: zapcodec backend)
      luxfi/accel  v1.1.4 (indirect) → v1.1.8 (transitive)

Constraint-compliance:

  - NO github.com/luxfi/codec imports in builder.go (verified by grep).
  - NO snow/avalanche/subnet naming.
  - All builder tests pass (go test ./...).
  - All parent genesis + cmd modules build clean.
  - Race detector clean.

TODO (wave-2g-wallet): once luxfi/proto tags include the zap_codec
subpackage, swap zap_codec.go's shim for the canonical
github.com/luxfi/proto/zap_codec import in builder.go and delete the
shim file.
2026-06-06 04:58:12 -07:00
Hanzo AI ce38b045d1 genesis/builder: thread codec through proto/p Wave 2A genesis API
proto/p Wave 2A (#101) rips github.com/luxfi/codec from p/genesis —
genesis.New, Genesis.Bytes and genesis.Parse now take a block.Codec
parameter at the call site (the same genesis-sized codec.Manager
already constructed inline elsewhere in the wallet stack).

Adds pvmGenesisCodec() helper mirroring the existing
newXVMParserCodecs() pattern in this file. Uses math.MaxInt32 budget
because PVM genesis blobs (full set of initial validator stake txs
plus CreateChainTx entries for X/C/D/Q/A/B/T/Z/G/K) can exceed runtime
tx-size limits. block.RegisterTypes seeds the full Apricot/Banff/
Durango/Quasar block + tx type set.

Threads pvmGenesisCodec() through:
  - FromConfig:    genesis.New + Genesis.Bytes  (one codec, two uses)
  - VMGenesis:     genesis.Parse
  - Aliases:       genesis.Parse

Also fixes the X-chain genesis serialization path that was broken by
Wave 1A: xvmCodecs.GenesisCodec was used directly without going
through xchaintxs.NewParser, so the canonical XVM tx types
(BaseTx/CreateAsset/Operation/Import/Export) and fx-owned
secp256k1fx.TransferOutput were never registered. The xvm genesis now
flows through NewParser, mirroring how UTXOAssetID already uses it.

builder_test.go's TestFromConfig_ChainSetIsShardDriven gains a single
pvmGenesisCodec() construction and threads it through both pgenesis.Parse
call sites.

go build ./... + go test ./... green.

The proto pin remains at v1.1.0; the workspace go.work consumes the
local proto/ which is on branch chore/proto-p-codec-rip with the new
API. This commit goes live once proto v1.2.0 is tagged.
2026-06-06 02:07:40 -07:00
Hanzo AI fb7c92270b inventory: brand-l1 IDs after 2026-06-06 clean rebootstrap
Architecture changes:
- RESET_TOKEN env-hack killed (use kubectl delete pvc instead)
- pars supply: 2T → 7B (matches chainId 7070/7071/7072 theme)
- spc supply: unified to 1B across all envs (matches mainnet RLP)
- All 3 envs wiped via PVC delete + re-bootstrapped clean
- RLP-only state restore: zoo, spc, C-Chain (mainnet) have archives

Mainnet C-Chain RLP import (1,082,780 blocks) running across 5
validators after this commit lands.
2026-06-06 00:24:09 -07:00
Hanzo AI 0f2aa9b698 inventory: testnet brand L1 IDs after 2026-06-05 quasar-fix re-bootstrap
Re-fired CreateChainTx for hanzo/zoo/pars/spc/osage on clean P-state after
pars-testnet genesis fix (added quasarTimestamp:253399622400 to satisfy
fork-ordering check). All 5 testnet brand L1s now isBootstrapped=true.

Cross-check via api.lux-test.network/ext/bc/<brand>/rpc all returning
chainId successfully. eth_blockNumber=0x0 (BOOTSTRAP-ONLY mode — block 1
will roll on first txn).
2026-06-05 17:01:01 -07:00
Hanzo AI cfd473d50d configs/testnet: sync genesis.cChainGenesis to A-form, scrub upgrade.json
Two related fixes, both validated live on do-sfo3-lux-k8s lux-testnet:

genesis.json:
  Replaced the embedded cChainGenesis field with the canonical 2-alloc
  A-form (matches standalone configs/testnet/cchain.json per 14b85b2).
  Old: gasLimit 0x3b9aca00, warpConfig blockTimestamp:0, embedded
  genesisHash + stateRoot annotations.
  New: gasLimit 0xb71b00, warpConfig blockTimestamp:1730517266,
  skipPostMergeFields:true. Empirically produces block 0 hash
  0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995 —
  the canonical RLP-matching hash from lux-testnet-96368.rlp.

upgrade.json:
  Dropped two entries that were breaking luxd boot:
    - warpConfig {blockTimestamp:0, requirePrimaryNetworkSigners:true}
      Fails luxd validation: "invalid precompile upgrades: PrecompileUpgrade
      (warpConfig) at [0]: disable should be [true]". Warp is already
      pre-allocated in the C-Chain genesis alloc at address 0x02..05
      (code:0x01, nonce:1), so an explicit upgrade entry is redundant and
      breaks block 0 identity if it touches state.
    - feeManagerConfig at blockTimestamp:900000000 (Sep 1998!)
      Fails luxd validation: "config block timestamp (900000000) <
      previous timestamp (1766708400)". The list is required to be
      monotonically non-decreasing; 900000000 placed mid-list between
      strictPQ entries at 1766708400 broke that. Mainnet doesn't have
      this entry; testnet inherited it as drift and it's never
      activated cleanly. Drop and re-introduce later via a properly
      ordered timestamp if/when fee admin is needed.

Live verification:
  - all 5 lux-testnet pods boot to block 0 = 0x1c5fe377…
  - RLP imports cleanly to h=218 (0xda)
  - external eth_blockNumber at api.lux-test.network/ext/bc/C/rpc → 0xda
2026-06-05 14:41:19 -07:00
Hanzo AI 26e6de4734 builder: cascade proto/x Wave 1A API changes
Wave 1A inverted the proto/x codec design — proto/x carries no codec
import; consumers supply linearcodec.NewDefault() + codec.NewManager
externally via the new xchaintxs.ParserCodecs struct.

Mirrors sdk/wallet/chain/x/constants.go::newXVMParserCodecs by adding
the equivalent helper here. Updates 3 call sites:
- xgenesis.NewGenesis(networkID, defs, codec) — new 3rd param
- xvmGenesis.Bytes(codec) — new param
- xchaintxs.NewParser(codecs, fxs) — new ParserCodecs first arg

genesis builds clean.
2026-06-05 13:37:58 -07:00
Hanzo AI 10a38d286d cmd: bump utxo to v0.3.7 + add local codec replace for zapcodec
Required for bootstrap-chain wire-compat with luxd v1.28.29. The
running node's wallet/network/primary uses utxo.ParseUTXO ZAP
dispatcher; the previous v0.3.4 emitted incompatible TypeKind/ShapeKind
discriminators (`wire: ShapeKind discriminator does not match expected
primitive shape`). Bump aligns the wire format.

Also adds local-codec replace because the published luxfi/codec@v1.1.4
does not contain the zapcodec package — that landed locally for the
2026-06 Wave 1D refactor and luxfi/node references it via the
zapcodec dispatch path.

inventory/brand-l1-2026-06-05-reset.json: post-reset canonical brand
L1 IDs. Supersedes brand-l1-2026-06-05.json (initial deploy with
orphan-prone naming).
2026-06-05 12:38:22 -07:00
Hanzo AI d02c329787 inventory: brand-genesis-homes.md — add osage row 2026-06-05 12:06:21 -07:00
Hanzo AI 6e59361e44 configs/inventory: brand genesis home inventory — one canonical per brand 2026-06-05 12:01:08 -07:00
Hanzo AI 99386d96f7 configs/_orphan-l2: tombstone — kill the AI-slop bucket
The _orphan-l2/ directory was created by #109 brand-genesis migration
as a transitional cohabitation for pars + spc (which didn't yet have
dedicated brand repos). Both brands NOW have proper homes:

  pars → ~/work/pars/genesis/networks/<env>/genesis.json
         (structured: chain.yaml + genesis.json + genesis.sha256 + precompiles.yaml)
  spc  → ~/work/spc/genesis/networks/<env>/genesis.json
         (mirrors pars layout)

Delete all 6 brand-genesis files from _orphan-l2 and the explanatory
README. Replace with a single MOVED.txt pointing readers at the brand
homes.

One way to do everything: brand genesis lives under the brand's own
workspace. State-repo at ~/work/lux/state/chains/ remains the empirical
provenance archive that brand homes track.
2026-06-05 12:00:44 -07:00
Hanzo AI de92c01c49 configs/_orphan-l2/spc-mainnet: sync to state-repo canonical
Mirrors the spc-mainnet genesis fix from
~/work/lux/state/chains/spc-mainnet/genesis.json into the deployment
mirror at configs/_orphan-l2/spc-mainnet/genesis.json. Same pattern as
zoo-universe commit 071afb4 (zoo h=799 RLP unblock).

Required so the 11-block spc-mainnet RLP archive
(b72f2602ec72360d742bf234a93f113b6614ba1f2d1935f63d8659bf61990ffc,
exported 2026-06-05) imports cleanly: the archive's block 1 parent
hash matches block 0 derived from the state-repo canonical genesis,
not the prior deployment-mirror variant.

Sync verified by `cmp` — files are now byte-identical.
2026-06-05 10:24:18 -07:00
Hanzo AI 5b6df656b1 docker: image build for cmd/rlp-{export,import} (closes #69 / #122 sidecar)
Dockerfiles for the two RLP utilities + GH workflow calling the
shared hanzoai/.github docker-build@main workflow. amd64-only,
lux-build-linux-amd64 runner pool per the cross-org isolation rule.

Consumed by the lux-operator ExportSchedules CronJob (#69) and the
TenantImports Job (#122). Both default to the in-cluster MinIO
endpoint at http://s3.lux-system.svc.cluster.local:9000.

Pre-existing modifications in builder/, configs/configs.go,
pkg/genesis/{config,networks,types,cert_policy}.go intentionally
left untouched per CTO commit-by-name discipline.
2026-06-05 09:54:56 -07:00
Hanzo AI 3294cbaa64 configs: move brand L1 genesis out of luxfi/genesis (Task #109)
luxfi/genesis only owns Lux primary-network genesis (mainnet/testnet/
devnet/localnet). Brand L1 genesis blobs now live next to the brand
that owns them, in each brand's universe repo:

  hanzo: hanzoai/universe → configs/genesis/{mainnet,testnet,devnet}/
  zoo:   zooai/universe   → configs/genesis/{mainnet,testnet,devnet}/

The pars-* and spc-* dirs have no separate universe repo yet — they
cohabitate this repo under configs/_orphan-l2/ until one is spun up,
explicitly marked as orphans (see configs/_orphan-l2/README.md).

This commit:
  - removes configs/hanzo-{mainnet,testnet,devnet}/genesis.json
    and configs/zoo-{mainnet,testnet,devnet}/genesis.{json,upgrade.json}
  - leaves MOVED.txt tombstones at each old path pointing at the
    new canonical home
  - moves configs/{pars,spc}-{mainnet,testnet,devnet}/ to
    configs/_orphan-l2/ (git mv — history preserved)
  - adds pkg/genesis/no_brand_shadow_test.go which fails if any
    genesis.json reappears under configs/hanzo-* / zoo-* / pars-* /
    spc-* at the top level
  - updates LLM.md + configs/mainnet/UPGRADE_NOTES.md doc refs to
    point at the new locations

Zero Go code references the moved paths — configs/configs.go
//go:embed only covers Lux primary (mainnet testnet devnet localnet),
not brand dirs. Verified by full pkg/genesis test pass.
2026-06-05 09:06:44 -07:00
Hanzo AI ff32c62328 cmd/rlp-export: admin_exportChain CLI wrapper (Task #118)
Symmetric with cmd/rlp-import. Thin HTTP client that:

  1. Idempotency precheck reads <output-file>.sentinel on the local PVC
     and short-circuits when Status=done + range matches — luxd is never
     contacted in the warm-cron case.
  2. Health-probes ${LuxdRPC}/ext/health (30s cap).
  3. POSTs admin_exportChain via /ext/bc/${chain}/rpc with KMS_AUTH_TOKEN
     bearer auth (same env-var contract as rlp-import).
  4. Maps Status → exit code:
       ok | noop      → 0
       interrupted    → 4 (operator re-runs with from-height=HighestExported+1)
       bad input      → 1
       luxd unreach   → 2
       admin RPC err  → 3

Lives in the cmd/ nested module per the existing rlp-import precedent.
Builds with GOWORK=off; Makefile target `make rlp-export` does this for you.

Tests cover: flag parsing, "latest" resolution, all four idempotency
fall-through branches, happy path + noop + interrupted server responses,
luxd unreachable, admin RPC error, KMS_AUTH_TOKEN bearer header
plumbing. 14 tests, all green.

Symmetric Makefile additions: `make rlp-export`, `make rlp-import`,
`make cmds`, `make test-rlp-export`.
2026-06-05 00:29:10 -07:00
Hanzo AI 6c3ba05cf8 cmd/rlp-import: operator-driven RLP import binary (Task #122)
Standalone Go binary that runs the six-step tenant RLP import lifecycle
in a single process: sentinel check → fetch → sha256 verify → wipe
chainData → POST admin.importChain → touch sentinel. Replaces the shell
loop split between luxd-startup.yaml's main container and the operator's
tenant-import init container.

The binary is invoked by luxfi/operator as a K8s Job — one per
LuxNetwork.spec.tenantImports[i]. Exit codes are wired for K8s backoff:
1=bad input/SHA mismatch/404, 2=luxd unreachable (transient retry),
3=admin.importChain RPC error. Content-hash addressing matches
TenantImportSpec.ContentHash byte-for-byte so the operator and the
binary compute identical sentinel paths.

Tests cover all six lifecycle steps + every failure exit code.
2026-06-05 00:23:27 -07:00
Hanzo AI 2773128389 mainnet/upgrade.json: cap dexConfig.maxPools 10000 → 2000
Per cryptographer review of #114 safe-subset precompile activation:
maxPools=10000 on mainnet C-Chain is a state-bloat risk (each pool
entry is permanent; 10000 slots at 32-byte keys + accounting blobs
balloons the trie). Devnet retains 10000 for fuzz / load testing.

Mainnet cap of 2000 still comfortably exceeds expected DEX-V4 pool
count (~50 canonical pairs + room for long-tail). Increase later via
forward-dated upgrade if usage approaches the cap.

routerConfig zero-address placeholders are a separate fix (#48 contract
deploy must land first; only then does it make sense to point routerConfig
at the real V3 quoter/factory + V2 router/factory addresses).

eciesConfig already excluded per #99.
2026-06-04 23:27:47 -07:00
Hanzo AI 9c1eff2804 configs/devnet/cchain.json: scrub eciesConfig per #99
ECIES on a public permissionless chain has a fatal usability flaw:
the recipient's private key must be passed in calldata so the
precompile can perform the discrete-log unwrap. Calldata is public,
so any genuinely-encrypted use leaks the private key on the first
unwrap — defeats privacy. Any cosmetic use (e.g. registering the
config but never calling decrypt) is a footgun waiting to fire.

Brand L2 cchain.json files (hanzo/zoo/pars/spc-mainnet/testnet/devnet)
were scrubbed in a prior patch; devnet/cchain.json was the lone Lux
primary copy that still carried the entry. This brings devnet in line
with mainnet (no eciesConfig in primary genesis) and matches the live
cluster CM state (verified empty across all 3 envs).

UPGRADE_NOTES.md at configs/mainnet/UPGRADE_NOTES.md documents the
exclusion rationale for posterity. Removal is final — eciesConfig
will not be re-added to any Lux-network chain.
2026-06-04 23:25:48 -07:00
Hanzo AI 38ebe7aa8b configs/inventory: brand-l1-2026-06-05.json — 12 brand chains across mainnet+testnet+devnet
Spawned via bootstrap-chain after luxfi/node v1.28.29 LockedOutput
dispatcher fix unblocked the deployer's P-chain UTXO balance
(was returning 0 because vms/components/lux had no branch for the
(TypeKindReserved, ShapeKindLockedOutput) envelope that
stakeable.LockOut.Bytes() writes for vesting allocations).

Each row records (brand, blockchain ID, per-chain network ID, EIP-155
EVM chain ID). Single source of truth for the post-deploy chain IDs;
downstream consumers (operator chain-aliases CM, explorer routes,
ingress, gateway) should read this inventory rather than re-deriving.

Deployer: BIP44 m/44'/9000'/0'/0/5 from LUX_MNEMONIC.

Bootstrap-chain run:
  /tmp/bootstrap-chain --uri=https://api.lux{,-test,-dev}.network \
    --hrp=lux|test|dev --bip44-idx=5 \
    --network-label=mainnet|testnet|devnet \
    --configs-dir=$ROOT/configs \
    --track-chain-ids=hanzo,zoo,pars,spc \
    --chain-name-override="hanzo=hanzo,zoo=zoo,pars=pars,spc=spc" \
    --skip-bootstrap-wait

(name override required because the content-hash default produced
"hanzo-b5ed62f6" which P-chain CreateChainTx rejected as "illegal
name character".)
2026-06-04 22:39:03 -07:00
Hanzo AI bc6aee460d mainnet/upgrade.json: monotone 47-entry reorder for warpConfig disable+enable
Empirical root cause of the wrong genesis block 0 hash on mainnet C-Chain
was that 18 precompiles were declared at blockTimestamp:0 — those activate
during Genesis.ToBlock() via ApplyPrecompileActivations, which mutates the
state trie (SetNonce + SetCode([0x01]) per precompile address). That state
trie mutation shifted block 0 stateRoot off the canonical 0x2d1cedac…
and consequently the canonical block 0 hash 0x3f4fa2a0… became 0x467015e9…

Fix: forward-date all 18 precompiles from blockTimestamp:0 → 1766708400
(the Quasar activation), so they activate AT the hard fork rather than
during genesis state setup. Block 0 is now byte-identical to the canonical
RLP and importChain accepts the chain on first try.

Structural reordering (47 entries total, strictly non-decreasing
blockTimestamp — validator requires monotone + warpConfig disable+enable
pair):

  [ 0] warpConfig disable @ 1766708399
  [ 1-25] aiMiningConfig … bls12381PairingConfig @ 1766708400
  [26] warpConfig enable @ 1766708400 (quorumNumerator=67,
       requirePrimaryNetworkSigners=true)
  [27-46] pulsarVerify … fixedPointMathConfig @ 1782864000 (PQ + extras)

Verified live on lux-mainnet C-Chain:
  block 0 hash = 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e ✓
2026-06-04 19:15:50 -07:00
Hanzo AI 05654162de genesis: re-add 7 bls12381*Config entries to mainnet upgrade.json
Re-introduces the 7 EIP-2537 BLS12-381 precompile entries that #114
(8a40886) had to drop from mainnet because all 7 sub-configs returned
the same ConfigKey via Key() — only one could land in the registry
and DisallowUnknownFields parse rejected the other six.

Fixed upstream:
  luxfi/precompile v0.5.35 (commit 794912f) — per-sub-config Key()
  luxfi/evm        v0.19.1 — bumps precompile dep
  luxfi/node       v1.28.23 — Dockerfile EVM_VERSION=v0.19.1

Activation: Quasar horizon Unix 1766708400 (2025-12-25 16:20 PT,
already past → active on next chain restart). Matches testnet's
existing 1766708400. Devnet stays at 0 (forever-active).

Cluster apply requires luxd image rebuilt from node v1.28.23+ tag
so the C-Chain plugin (built from evm v0.19.1) accepts the parse.
2026-06-03 12:35:25 -07:00
Hanzo AI a5732479a7 configs: kill etnaTimestamp; one and only one upgrade-key namespace
Quasar Edition rip. Reverts e6cec59 (the deployment-skew bandage) now
that luxfi/evm v0.19.0 ships:
 - QuasarTimestamp Go field with json:"quasarTimestamp" tag
 - DisallowUnknownFields strict decode at upgradeBytes parse

There is one name. quasarTimestamp. The strict decoder turns any
remaining etnaTimestamp residual into a loud parse error at boot
instead of a silent fork-disable at activation.

Brand L1 EVM genesis (hanzo/pars/spc/zoo mainnet) and primary mainnet
upgrade.json: etnaTimestamp removed; quasarTimestamp: 0 retained.

New envs filled to canonical (was missing, hidden by the skew):
 - configs/devnet/upgrade.json
 - configs/local/upgrade.json
 - configs/localnet/upgrade.json
All carry quasarTimestamp:0 + every PQ precompile + cipher precompile
at blockTimestamp 0 (always-on for dev surfaces).

Mainnet keeps strictPQTimestamp:1766708400 (Dec 25 2025 16:20 PT)
as the forward-dated profile gate; PQ precompiles already active
from block 0.

Pairs with: luxfi/evm v0.19.0 + luxfi/node v1.28.22 bump.
2026-06-03 11:51:44 -07:00
Hanzo AI e6cec59f35 mainnet/upgrade.json: revert quasarTimestamp → etnaTimestamp
Commit af356f7 (Etna → Quasar Edition) renamed the field in source
but the deployed EVM plugin at s3://lux-plugins/v1.1.0/ is built
from luxfi/evm@v0.18.18 which still reads NetworkUpgrades.EtnaTimestamp
(json tag etnaTimestamp). Until a new plugin ships with the renamed
struct, mainnet upgrade.json must use the pre-rename key — otherwise
the override silently drops and the plugin's verifyNetworkUpgrades
trips "etnaTimestamp not enabled, but fortunaTimestamp enabled" for
every brand L1 EVM that inherits this override structure.

Matches the on-disk content active on lux-mainnet luxd pods
(/data/configs/chains/C/upgrade.json, applied via the luxd-startup
ConfigMap patch on 2026-06-03). When the plugin upgrade lands the
key flips back to quasarTimestamp.
2026-06-03 10:04:51 -07:00
Hanzo AI 8a40886c25 mainnet/upgrade.json: fix C-Chain warpConfig + drop bls12381 entries
Two surgical fixes that get lux-mainnet C-Chain past the
`PrecompileUpgrade verification` gate in luxd v1.28.21 (evm@v0.18.18):

1. warpConfig toggle (disable@1766708399 + enable@1766708400)
   C-Chain genesis already enables warp at blockTimestamp 1730446786
   (luxfi/genesis@v1.7.1 cchain.json). Per the validator at
   evm/params/extras/precompile_upgrade.go:133, the next upgrade for
   an enabled precompile MUST flip state — a re-enable without an
   intermediate disable trips "disable should be [true]". Inserting a
   disable upgrade 1s before Quasar (1766708399) followed by re-enable
   at Quasar (1766708400) satisfies the toggle requirement and
   activates the new warp config (requirePrimaryNetworkSigners=true)
   at the network-wide PQ rollout, consistent with #114 intent.

   Also moved the two warp entries to AFTER the at-zero precompile
   block so the monotonicity check at line 129 (timestamps must be
   non-decreasing across the entire list) passes.

2. Dropped bls12381 sub-config entries (G1Add/Mul/MSM, G2Add/Mul/MSM,
   Pairing). All 7 share a single Configurator.MakeConfig() in
   luxfi/precompile@v0.5.23/bls12381/module.go that returns a Config
   whose Key() method hard-codes G1AddConfigKey:

       func (c *Config) Key() string { return G1AddConfigKey }

   This makes the EVM plugin's verifyPrecompileUpgrades loop see all
   7 entries as "bls12381G1AddConfig", the 2nd-7th fail the
   disabled/enabled toggle check with "disable should be [true]" even
   though the entries are byte-distinct in the JSON.

   Underlying fix is in luxfi/precompile (instance-field key like
   `dead`/`graph`/etc — not a const). Until that lands and a new
   EVM plugin ships to s3://lux-plugins/, mainnet upgrade.json must
   not enumerate bls12381 sub-modules. The plugin still gates them
   correctly via strictPQTimestamp; the loss is only explicit
   activation scheduling for the bls12381 sub-keys.

After these two fixes lux-mainnet C-Chain bootstraps cleanly
(info.isBootstrapped=true, /ext/health C check OK).
2026-06-03 09:42:01 -07:00
Hanzo AI 7d4eab387a mainnet: fix C-Chain warpConfig + brand L1 fork-ordering
Surgical fixes for 4 RED chains on lux-mainnet:

1. configs/mainnet/upgrade.json — C-Chain
   warpConfig[0]: blockTimestamp 0 → 1766708400 (Quasar Edition Unix)
   luxd v1.28.18 rejected blockTimestamp:0 without disable:true at
   genesis because C-Chain genesis already configures warp. Forward-
   dating activation to Quasar (consistent with #114 intent: warp
   activates with PQ precompiles at the network-wide PQ rollout).

2. configs/{hanzo,pars,spc}-mainnet/genesis.json
   Added etnaTimestamp:0 between durangoTimestamp and quasarTimestamp.
   Deployed luxd v1.28.18 (luxfi/evm@v0.18.18) field names predate the
   Etna→Quasar rename — its NetworkUpgrades struct still uses
   EtnaTimestamp json:"etnaTimestamp". Without it, fork-ordering check
   in evm@v0.18.18 params/extras/config.go:407 fails:
   "etnaTimestamp not enabled, but fortunaTimestamp enabled at
   timestamp 0x..." Adding etnaTimestamp:0 satisfies the binary's
   ordering check; quasarTimestamp stays as forward-compat alias for
   post-rename binaries.

zoo-mainnet/genesis.json unchanged (has only evmTimestamp +
durangoTimestamp, no later forks → no ordering issue → already GREEN).
2026-06-03 09:05:20 -07:00
Hanzo AI af356f789a rename: Etna → Quasar Edition
- pkg/genesis/types.go ChainConfig.EtnaTimestamp → QuasarTimestamp
    json tag etnaTimestamp → quasarTimestamp
  - configs/{mainnet,testnet,devnet}/{cchain,upgrade}.json
    "etnaTimestamp" → "quasarTimestamp"
  - configs/{hanzo,zoo,pars,spc}-{mainnet,testnet,devnet}/genesis.json
    "etnaTimestamp" → "quasarTimestamp"
  - configs/zoo-mainnet/upgrade.json
    "etnaTimestamp" → "quasarTimestamp"
  - scripts/gen-localnet.py + LLM.md docs cleanup

No backcompat aliases — forwards-only. All embedded genesis +
upgrade JSON blobs flipped in-place.
2026-06-02 23:11:39 -07:00
Hanzo AI 8a10a3873e cmd/bootstrap-chain: read LUX_MNEMONIC env (one var, one way)
The earlier MNEMONIC unification commit (4383ad4) missed this CLI tool;
it was still reading the legacy MNEMONIC env name directly via
os.Getenv. Per the "one var, one way" rule the canonical name is
LUX_MNEMONIC across every code path in the genesis repo.

Updated in cmd/bootstrap-chain/main.go:
- Package-doc usage example: MNEMONIC="..." -> LUX_MNEMONIC="..."
- --print-addr-only flag help: scrub MNEMONIC -> LUX_MNEMONIC
- --key-file flag help + comments: MNEMONIC -> LUX_MNEMONIC
- loadKey() reads os.Getenv("LUX_MNEMONIC") instead of "MNEMONIC"
- Error message references LUX_MNEMONIC

No backcompat fallback: callers using the legacy MNEMONIC env get the
clear "LUX_MNEMONIC env var or --key-file required" error.

Verification:
- go build ./...           -> clean
- go vet ./...             -> clean
- go test ./... -count=1   -> ok configs, ok pkg/genesis
2026-06-02 22:43:57 -07:00
Hanzo AI 30f300f9ea genesis: scrub Avalanche residue from pkg + cmd + LLM.md prose
Comment-only scrub of upstream-fork residue in user-facing docstrings,
CLI help text, and LLM.md prose. No behavior change; no wire-format
change.

pkg/genesis/keys.go:
- DefaultNumAccounts doc: drop "AvalancheJS" from canonical-BIP44 wallet
  list (was "Core, MetaMask, AvalancheJS, lux key derive" -> now
  "Lux Wallet, MetaMask, lux key derive").
- LoadKeysFromMnemonic doc: drop "Avalanche/Lux BIP44 path" (just
  "Lux BIP44 path") and "Core Wallet, AvalancheJS" wallet list (now
  "MetaMask, Lux Wallet").
- LoadKeysFromMnemonic inner loop comment: same scrub
  ("Standard Avalanche/Lux secp256k1 child" -> "Standard Lux ...";
  "matches Core Wallet, MetaMask, cast" -> "matches Lux Wallet, ...").
- BuildBIP44WalletAllocations doc: drop "SLIP-0044 coin type Lux
  inherits from its upstream lineage" prose. Replaced with brand-
  neutral "BIP44 coin type Lux uses by canonical convention".

cmd/genesis/main.go:
- bip44-wallet-keys flag help: drop "SLIP-0044 coin type 9000" ->
  "BIP44 coin type 9000 canonical convention".

LLM.md:
- "Fortuna Timestamp Fix" prose: drop "Fortuna, Etna, and Granite
  upgrades" naming. Replaced with "pre-Quasar Edition upgrades"
  framing. The JSON field names (etnaTimestamp / fortunaTimestamp /
  graniteTimestamp) are preserved in the example block because they
  are the wire format consumed by luxfi/evm.

Intentionally kept (per task scope and forward-compat realities):
- params_test.go:33 + params.go:303 "avax HRP" rejection guards —
  these are protective security tests/comments, not residue. The whole
  point is to refuse "avax"-HRP bech32 addresses synthesized from a
  canonical lux validator's 20 bytes. Stripping the "Avalanche" prose
  would obscure why the guard exists.
- pkg/genesis/types.go:229 EtnaTimestamp uint64 field — this Go field
  is a downstream mirror of the wire-format identifier defined at the
  source of truth in luxfi/evm/params/extras/network_upgrades.go and
  consumed by ~95 JSON genesis blobs, K8s startup manifests, academy
  docs, and migration tooling. Renaming the genesis-repo mirror alone
  would break JSON round-trip with every other consumer in the
  ecosystem. A wire-format rename of EtnaTimestamp -> QuasarTimestamp
  must originate in luxfi/evm and migrate every embedded genesis
  config + every k8s manifest + every doc in one coordinated PR series.
  That migration is OUT OF SCOPE for this commit and is reported back
  to the dispatcher; see external-caller surprises below.

Verification:
- go build ./...           -> clean
- go vet ./...             -> clean
- go test ./... -count=1   -> ok configs, ok pkg/genesis
- grep '[Aa]valanche\|AvalancheJS\|Core Wallet\|SLIP-0044' pkg/genesis
  cmd LLM.md
    -> only 2 lines remain (params.go:303 + params_test.go:33),
       both inside the protective HRP-rejection guard prose.
2026-06-02 22:41:28 -07:00
Hanzo AI 4383ad4583 genesis: unify mnemonic env to LUX_MNEMONIC + kill vesting docstring rot
One env, one way. The genesis mnemonic is now read from exactly one
env var: LUX_MNEMONIC. The prior two-env fallback chain
(MNEMONIC > LIGHT_MNEMONIC) is gone. Per-env isolation is by a
DIFFERENT mnemonic per env (loaded from KMS), not by env-var name.

pkg/genesis/keys.go:
- New MnemonicEnvVar = "LUX_MNEMONIC" constant.
- getMnemonicEnv() reads only LUX_MNEMONIC; no fallback.
- LightMnemonic constant kept (it's the well-known dev seed VALUE;
  pass it as the value of LUX_MNEMONIC for local nets).
- IsProductionNetwork docstring updated to spell out the canonical 4:
  mainnet(1) / testnet(2) / devnet(3) refuse public mnemonics;
  local(1337)+ allows LightMnemonic.
- Docstring rot purged: the prior "50M free + 50M vesting at 1%/year
  over 100y" claim was a lie — buildConfigFromKeyInfos never wired the
  100-period schedule (the code admits the schedule "overflows zapdb
  batch limit"). Replaced with the truth: 1000 × 50M LUX on X-Chain
  AND P-Chain, immediate spend; C-Chain is treasury-only (2T LUX).
- Dead code deleted: StakingStartTime, UnlockInterval consts,
  buildUnlockSchedule() function.

pkg/genesis/allocations.go:
- VestingConfig struct removed.
- DefaultVesting() removed.
- WithVesting() builder method removed.
- Vesting branches in PChain() / PChainMap() collapsed to immediate-only.
- MainnetAllocations no longer calls WithVesting(DefaultVesting()).

pkg/genesis/validator_keys.go:
- VestingPeriods const removed.
- GeneratePChainAllocationsWithVesting() removed.

pkg/genesis/networks.go (new):
- Canonical primary-network-id table: MainnetID / TestnetID / DevnetID
  / LocalID = 1 / 2 / 3 / 1337. Matching C-Chain (EVM) chain IDs:
  96369 / 96368 / 96370 / 31337.
- IsCanonicalPrimaryNetwork helper.

cmd/checkkeys/main.go:
- Reads LUX_MNEMONIC via genesis.MnemonicEnvVar (no fallback).

cmd/derive100/main.go:
- Usage hint updated: mnemonic-env: LUX_MNEMONIC.

cmd/genesis/main.go:
- Flag help, env-var docs, examples, and error messages all reference
  LUX_MNEMONIC.

pkg/genesis/light_mnemonic_guard_test.go:
- All t.Setenv calls use LUX_MNEMONIC.
- Test labels reference LightMnemonic (the constant) not the dead env name.

configs/getgenesis_test.go:
- Comment updated: LightMnemonic instead of LIGHT_MNEMONIC.

CHANGELOG.md + LLM.md:
- Documented the unification + the dead-code removal.

The validator stake-lock (3-entry 5y/10y/20y schedule attached to the
validator's UTXO inside buildConfigFromKeyInfos at keys.go:1077-1083)
is unchanged — that locked-stake bucket is the only UnlockSchedule in
the canonical genesis path and the ProtocolVM needs it.

External-caller sweep: grepped ~/work/lux, ~/work/hanzo, ~/work/zoo,
~/work/luxfi for genesis.VestingConfig / genesis.DefaultVesting /
genesis.GeneratePChainAllocationsWithVesting / genesis.StakingStartTime
/ genesis.UnlockInterval — zero hits. Safe to delete. (luxfi/keys at
~/work/lux/keys has its own MainnetAllocations copy — separate repo,
not affected.)

Verification:
- go build ./... → clean
- go vet ./... → clean
- go test ./... -count=1 → ok configs, ok pkg/genesis
- grep '[^_X]MNEMONIC\|LIGHT_MNEMONIC' under pkg/genesis cmd configs →
  zero matches (only CHANGELOG history + Python script local vars).
- grep VestingConfig|buildUnlockSchedule|GeneratePChainAllocationsWithVesting
  under source → zero matches.
2026-06-02 22:21:27 -07:00
Hanzo AI 210eb824e7 configs: remove {brand}fix-{env}/ symlinks (workaround retired)
The fix-suffix symlinks (hanzofix-testnet, zoofix-devnet, etc.) were a
workaround for P-chain CreateChainTx name-collision against stale chain
rows from earlier bootstrap attempts. They duplicated the genesis blob
under a second path per (brand, env) pair, violating the "one canonical
way" rule.

Now that bootstrap-chain derives chain.name from the canonical genesis
content (commit 6046cdb), collision is impossible and the workaround
has no reason to exist. The configs/ directory is back to one canonical
{brand}-{env}/ per pair (4 brands x 3 envs = 12 directories).

11 symlinks removed:
  hanzofix-{devnet,testnet,mainnet}
  zoofix-{devnet,mainnet}
  spcfix-{devnet,mainnet}
  parsfix-{devnet,testnet,mainnet}

(spcfix-testnet was never created; zoofix-testnet was already gone.)
2026-06-02 20:46:20 -07:00
Hanzo AI 6046cdb590 cmd/bootstrap-chain: decomplect chain.name from brand identity
The CreateChainTx `name` argument is a P-chain-unique identifier — caller's
choice. The brand alias ("hanzo", "zoo", "spc", "pars") is user-facing and
belongs at the chain-aliases ConfigMap layer. Conflating the two forced
the {brand}fix-{env}/ symlink workaround when re-bootstrapping over stale
P-chain chain rows from prior CreateChainTx attempts.

Default chain.name is now content-addressed: "<brand>-<8hex>" where
8hex = hex(SHA256("<brand>|<env>|"||genesisBytes))[:4]. Properties:

  - Idempotent: same canonical genesis → same chain.name → re-runs detect
    the existing chain via platform.getBlockchains and skip CreateChainTx.
  - Collision-free: different genesis bytes (any change to chainId, alloc,
    config) → different chain.name → CreateChainTx succeeds against
    P-chain instead of colliding with the previous stale row.
  - Brand-rooted: "hanzo-3f4a1c8b" stays humanly legible without conflating
    the chain.name with the user-facing brand alias.

Spec struct renamed: Name → Brand (user-facing) + ChainTxName (P-chain ID).
Result JSON gains "brand" and "chainTxName" fields so downstream
consumers (chain-aliases CM, explorer-chains CM, gateway routes) can pin
on the brand value without parsing the chain.name suffix.

Override path: --chain-name-override "<brand>=<name>,..." pins explicit
names for legacy reattachment scenarios.

4 new tests pin the contract:
  - Deterministic (same input → same output)
  - DifferentGenesisDifferentName (bytes change → name changes)
  - DifferentBrandDifferentName (brand-scoped)
  - DifferentEnvDifferentName (env-scoped)

All 14 tests pass.
2026-06-02 20:46:09 -07:00
Hanzo AI a1499e9b70 configs: add hanzofix/zoofix/spcfix/parsfix-{testnet,devnet,mainnet} symlinks
After Track D recovery (2026-06-02), brand chain names hanzo/zoo/spc/pars
were already taken on P-chain — CreateChainTx rejects duplicate names.
The 2026-05-27 recovery introduced "zoofix" + "spcfix" as the re-fire
chain names for testnet; this commit extends the same pattern to all
four brands × all three envs, with symlinks pointing at the canonical
brand-{env}/genesis.json blobs.

This lets bootstrap-chain --track-chain-ids=hanzofix,zoofix,spcfix,parsfix
re-fire CreateChainTx with the same canonical genesis without name
collision.

Rolled in production 2026-06-03 03:00-04:00 UTC:
- testnet: hanzofix=2jDUTLY5qiu6F9HqZxPSJ5EZ6YXgitDvdKrouPb49pBA89CSgE,
           parsfix=2iGzgAvBDh5W4duZV6TPjjaDnnZ85pXSvaTuEMUZBCqTUz9nJx
           (zoofix + spcfix were already in place from 2026-05-27)
- devnet:  hanzofix=2Jtt431M99BpEVzRCv9KKNRdyUtkYadA3yzXxzfbPhXGwLTT3g,
           zoofix=22xVZa9Kt74gh5NvGkePT4hYYWNoDApiznwx8a4z9WYVKG9fYZ,
           spcfix=2Vw5ohskeRoHeTgQn3Zxdp3KSc9zqkvaCBvCA1ek6d6VKCSuuZ,
           parsfix=ySWSTEgr2ZdVcpU1gNwAnPeXWFthVHW2TmFdEMSFy4kFNpTML
- mainnet: BLOCKED — legacy LUX vs staking asset fee mismatch
           (separate asset-migration tx required first).
2026-06-02 20:30:31 -07:00
Hanzo AI 57470516de cmd/bootstrap-chain: Track D regression gate — refuse genesis with embedded stateRoot/genesisHash
Close the Track D root cause that produced empty 0xa6aecc6c... state
roots on testnet brand L2s (hanzo, zoo, spc, pars). Pre-existing brand
L2 JSONs under configs/<brand>-<env>/genesis.json are already clean,
but nothing prevented a future generator (or a hand-edit cribbed from
the primary network's testnet/genesis.json embedded cChainGenesis blob)
from reintroducing those fields.

The fields override the result of luxfi/geth/core/genesis.go::
Genesis.Commit's flushAlloc — a stale value silently displaces the
canonical alloc trie, then the L2 boots with block 0 stateRoot pointing
at a trie node that was never persisted. That's the exact failure mode
producing "missing trie node 0x2d1cedac..." errors on the existing
testnet hanzo L2.

Changes:
- cmd/bootstrap-chain/main.go: hard-refuse any genesis carrying a
  non-empty stateRoot or genesisHash before any CreateChainTx burns
  LUX. Error names the specific geth/core/genesis.go lines and the
  Track D context so the operator can fix at source.
- cmd/bootstrap-chain/main_test.go: TestBrandL2GenesisHasNoStateRoot-
  OrGenesisHash walks every <brand>-<env>/genesis.json under configs/
  and asserts the absence of both fields. Test self-checks that at
  least one brand L2 dir was found so a path bug can't silently pass.

The historical Lux primary C-Chain header workaround that needed these
overrides is dead — the empirical block-0 hash now lives in
configs/mainnet/cchain.json. Brand L2 JSONs MUST stay clean.

Authorized by 2026-06-02 destructive recovery sweep — Track D root
cause closure.
2026-06-02 19:03:10 -07:00
Hanzo AI 13dea017d6 configs(testnet): scrub 3 unregistered EIP precompile keys (task #99)
The primary-testnet C-Chain upgrade schedule referenced three precompile
keys that the pinned luxfi/precompile v0.5.27 ships without
RegisterModule():

  - kzg4844Config
  - secp256r1Config
  - ed25519Config

extras.UpgradeConfig.UnmarshalJSON rejects unknown ConfigKeys with
"unknown precompile config: <key>", so luxd would refuse to start the
testnet C-Chain VM at the next pod restart. The mainnet copy was
scrubbed in the same patch series (configs/mainnet/upgrade.json).
zoo-mainnet's standalone upgrade.json is out of scope here and will be
addressed under its own task.

47 entries remain (was 50): warpConfig + 19 net-new at the Quasar
Edition timestamp 1766708400 + 27 forward-dated cryptography
precompiles also at 1766708400. JSON re-parsed and validated.

Followup: re-add the three keys after bumping luxfi/precompile to a
version that declares RegisterModule() for kzg4844, secp256r1, and
ed25519 (mirroring the mainnet plan in UPGRADE_NOTES.md §Followup).
2026-06-02 18:37:41 -07:00
Hanzo AI 32d23f1aa1 #114: drop 3 EIP precompiles + scrub eciesConfig from 11 brand genesis (forward-dated 2026-07-01 activation)
upgrade.json: 46 precompileUpgrades (was 49). Removed 3 unregistered
EIP entries surfaced by Red round-2 vector V19. All non-warp PQ/crypto
primitives forward-dated to blockTimestamp 1782864000 (2026-07-01
UTC) per #114 v3. evmTimestamp/durango/etna/fortuna/granite hold at 0
(genesis-active); strictPQTimestamp 1766708400 (2025-12-26 00:20 UTC)
unchanged.

Brand L2 genesis (hanzo/pars/spc/zoo × devnet/mainnet/testnet, 11
files): scrubbed legacy eciesConfig fields — never registered in the
canonical precompile table, would refuse-init when checkPrecompile
Compatible ran on next pod recreate. zoo-mainnet was cleaned in
218ff42 and is unchanged here.
2026-06-02 18:37:02 -07:00
Hanzo AI 3354940651 configs(mainnet): activate 30 safe-subset precompiles forward-dated 2026-07-01 UTC
Task #114 — extend the Lux primary C-Chain upgrade.json with the 30 missing
safe-subset precompiles, forward-dated to blockTimestamp=1782864000
(2026-07-01 00:00 UTC = 29 days from authorship 2026-06-02, exceeding the
14-day validator-buffer minimum). Brand L2s (hanzo/zoo/pars/spc) already
carry these in their block-0 genesis; this catches the primary network up.

Safe subset framing:
- Brand L2 genesis = 43 precompiles (~/work/lux/genesis/configs/hanzo-mainnet/genesis.json).
- Primary C-Chain = 42 (brand L2 minus eciesConfig) + 3 standard EIP
  precompiles brand L2s don't carry (kzg4844Config, secp256r1Config,
  ed25519Config) + 3 burn handlers (dead*Config) + warpConfig.

Total after patch: 49 entries:
- 1 warpConfig at blockTimestamp=0 (genesis pre-alloc at 0x0200…0005).
- 18 already-live precompiles at blockTimestamp=0 (aiMining, blake3,
  cggmp21Verify, dead*x3, dexConfig, routerConfig, fheConfig, frostVerify,
  graphConfig, hpkeConfig, mldsaVerify, mlkemConfig, pqcryptoConfig,
  ringConfig, slhdsaVerify, zkConfig — the set inlined in
  ~/work/lux/universe/k8s/lux-mainnet/luxd-startup.yaml UPGRADE_JSON).
  These MUST stay at blockTimestamp=0 — luxd's checkPrecompileCompatible
  refuses to boot if an already-live precompile is rescheduled.
- 30 safe-subset additions at blockTimestamp=1782864000 (2026-07-01 UTC):
  Pulsar + Magnetar + P3Q + Corona threshold; HQC; BLS12-381 G1/G2/MSM/
  Pairing x7; Curve25519, X25519, Sr25519, BabyJubJub, Pedersen, Poseidon,
  Pasta, X-Wing; Compute Market, Bridge Registrar, StableSwap, VRF,
  Attestation, Anchor, FixedPointMath, KZG-4844, secp256r1, Ed25519.

Exclusions:

- eciesConfig — deliberately excluded per the rationale in
  ~/work/lux/precompile/x25519/contract.go (lines 17-23). ECIES requires
  the recipient's private key in calldata; calldata is world-readable on
  a public chain, irrecoverably exposing the key. Symmetric (AES,
  ChaCha20) precompiles were removed for the same reason. X25519/ML-KEM/
  HQC/X-Wing are retained because they expose only ciphertexts and
  ephemeral public material, never secrets.

- contractDeployerAllowListConfig, contractNativeMinterConfig,
  txAllowListConfig, rewardManagerConfig — admin-gated allowlists. The
  primary C-Chain is permissionless by design; activating these would
  hand admin to an arbitrary address (trust hazard) or activate empty
  (dormant gate, strictly worse). They remain available for subnet/L2
  activation where an admin role is appropriate.

- feeManagerConfig — removed from canonical. Previously parked at
  blockTimestamp=900000000 (1998) as a registry-slot placeholder; that
  placement violates the monotonicity rule enforced by
  extras.ChainConfig.verifyPrecompileUpgrades. Activate via a separate
  forward-dated governance event with admin addresses explicitly assigned
  if needed; the chain-config feeConfig is already pinned directly.

Strict-PQ profile gate (NetworkUpgradeOverrides.strictPQTimestamp =
1766708400) activates contract.StrictPQReporter on the ChainConfig from
the Quasar Edition activation (2025-12-25 16:20 PST). All classical
pairing-based / discrete-log precompiles call
contract.RefuseUnderStrictPQ(state) at the top of Run() (verified across
bls12381 + secp256r1 module.go dispatchers + babyjubjub / pasta /
pedersen / poseidon / curve25519 / x25519 / sr25519 / ed25519 / frost /
cggmp21 / kzg4844 contract.go). The 30 forward-dated activations all
fall after strictPQTimestamp, so no window exists where a classical
primitive executes permissively on the primary network.

Tests (luxfi/evm params/extras):

- TestMainnetUpgradeJSON_IsForwardCompatibleWithLiveActivations PASS
  (every already-live precompile preserved at the same blockTimestamp).
- TestMainnetUpgradeJSON_PrecompileTimestampsAreMonotonic PASS
  (0 → 0 → ... → 0 → 1782864000 → ... → 1782864000, monotonic).
- TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners PASS
  (warpConfig.requirePrimaryNetworkSigners=true).
- TestMainnetUpgradeJSON_HasStrictPQActivation PASS
  (strictPQTimestamp=1766708400).
- TestMainnetChainConfig_HasStrictPQTrue PASS (chain-configs side).

UPGRADE_NOTES.md sidecar documents the exclusion rationale, the activation
tier table, the strict-PQ posture, and a red-review checklist.

RED REVIEW REQUIRED — do not push to origin without sign-off. Mainnet
primary network upgrade is consequential.

Follow-up (separate PRs, out of scope):
- ~/work/lux/universe/k8s/lux-mainnet/luxd-startup.yaml currently writes
  the same UPGRADE_JSON to all 5 EVM chain config dirs (C + brand L2s);
  brand L2s already carry these precompiles in their block-0 genesis, so
  the override is at best a no-op and at worst a wedge. Patch to scope
  the inline upgrade.json to the primary C-Chain only and refresh its
  contents from this canonical file.
2026-06-02 14:03:06 -07:00
Hanzo AI d712fad7f9 cmd/bootstrap-chain: add --key-file flag (raw secp256k1 alt to MNEMONIC+BIP44)
Operators that already hold a 32-byte hex secp256k1 deployer key on disk
(e.g. ~/work/lux/keys/deployer-test-dev/private.key used to fund the
test+dev primary genesis allocations) can pass --key-file <path> instead
of MNEMONIC + --bip44-idx. MNEMONIC path remains the default.

Drove the brand-L2 CreateChainTx registration for testnet + devnet
(closes universe #168) — 8 chains, all bootstrapped 3/3 pods per env.

go.mod: add local replace github.com/luxfi/node => ../../node to pick up
the wallet's P-only fail-soft (lux/node@HEAD). Drop the replace once
luxfi/node tags the change. tidy refreshed transitive deps (accel v1.1.4
→ v1.1.8, local genesis v1.12.19 → v1.13.8) — these are no-op for the
bootstrap-chain logic.
2026-06-02 13:49:08 -07:00
Hanzo AI 435ba69bbb chainid: add EVMChainIDFor(brand, env string) string-input helper
LP-018 §Canonical EVM chainID map specifies the (brand × env) → uint64
table. The existing typed lookup EVMChainID(family, env) takes a
NetworkFamily + uint32; CRD validators, admission webhooks, and
operator-side helpers often hold brand+env as raw strings instead.

EVMChainIDFor wraps EVMChainID with case-insensitive string parsing for
both brand and env ("mainnet"|"main", "testnet"|"test", etc.). No
parallel data table — one source of truth.

Also retire stale "sovereign=true|false per LP-182" docstrings on
Family* constants; the locked NetworkSpec carries only networkID +
validators, and FamilyLiquid/FamilyPars can run as either Anchored
(validators > 0 on a Lux primary) or Independent (own networkID).

Update LP-182 references to LP-018 (LP-182 archived).
2026-06-02 12:01:03 -07:00
Hanzo AI 266de9e8af configs: LP-182 §Appendix A canonical EVM chainID map
EVMChainID(family, env) is the source-of-truth lookup that operator
CRs and per-network genesis blobs must agree with. Total over the closed
set published in LP-182:

  Lux   x {mainnet,testnet,devnet,local} = {96369, 96368, 96370, 31337}
  Hanzo x {mainnet,testnet,devnet}       = {36963, 36962, 36964}
  Zoo   x {mainnet,testnet,devnet}       = {200200, 200201, 200202}
  SPC   x {mainnet,testnet,devnet}       = {36911, 36910, 36912}
  Liquid x {mainnet,testnet,devnet}      = {8675309, 8675310, 8675312}
  Pars  x {mainnet,testnet,devnet}       = {7070, 494950, 494951}

NetworkFamilyOf(chainID) provides the inverse lookup so a CR validator
can detect an evmChainID typo early. ParseNetworkFamily is
case-insensitive on free-form input.

Unknown (family, env) cells return (0, false) — never a silent zero.
2026-06-02 11:22:23 -07:00
Hanzo AI c84a71812f pkg/genesis/security: go mod tidy (accel v1.1.4→v1.1.8) 2026-06-02 04:24:23 -07:00
Hanzo AI 14b85b2464 configs(testnet): revert cchain.json to RLP-matching 2-alloc form + sibling upgrade.json
Empirically verified 2026-06-02 via ~/work/lux/state/cmd/rlp-vs-genesis:

  RLP block 1 parentHash: 0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995
  Reverted genesis.json:  0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995
  Result:                 MATCH

Same anti-pattern as mainnet (ae7f530) and zoo-mainnet (218ff42): HEAD had 43
precompiles baked into config.precompileUpgrades at blockTimestamp:0, producing
a non-canonical hash that does NOT match the historical RLP.

Reverted to pristine 2-alloc form (verified against lux-testnet-96368.rlp):
  - treasury 0x9011E888...
  - warp pre-alloc at 0200000000000000000000000000000000000005 (code:0x01, nonce:1)
  - timestamp 0x67259912 (1730517266) — distinct from mainnet's 0x672485c2
  - gasLimit 0xb71b00, baseFeePerGas 0x5d21dba00
  - no precompileUpgrades in config (forward-dated activations go in upgrade.json)

upgrade.json: sibling file (copy of mainnet's) activating all 43 PQ precompiles
at blockTimestamp 1766708400 (Dec 25 2025 16:20 PST). warpConfig stays at
blockTimestamp:0 because the alloc already pre-bakes its address. Preserves block
0 identity (so RLP imports cleanly) while activating PQ precompiles at the
network upgrade timestamp.
2026-06-02 04:20:19 -07:00
Hanzo AI 218ff421fc configs(zoo-mainnet): revert genesis.json to RLP-matching pristine form + add sibling upgrade.json
Empirically verified 2026-06-02 via ~/work/lux/state/cmd/rlp-vs-genesis:

  RLP block 1 parentHash: 0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933
  Reverted genesis.json:  0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933
  Result:                 MATCH

Previous HEAD had 43 precompiles baked into config.precompileUpgrades at
blockTimestamp:0 — same anti-pattern as the mainnet C-Chain wedge — producing
non-canonical 0x24ac5ff9... that does NOT match the historical RLP.

Reverted to the pristine 2-alloc form (verified empirically against
~/work/lux/state/rlp/zoo-mainnet/zoo-mainnet-200200.rlp):
  - treasury 0x9011E888...
  - warp pre-alloc at 0200000000000000000000000000000000000005 (code:0x01, nonce:1)
  - timestamp 0x6727e9c3, gasLimit 0xb71b00, baseFeePerGas 0x5d21dba00
  - no precompileUpgrades in config (forward-dated activations go in upgrade.json)

upgrade.json: sibling file activating all 43 PQ precompiles at blockTimestamp:
1766708400 (Dec 25 2025 16:20 PST per network_activation_2025_12_25.md).
warpConfig stays at blockTimestamp:0 because the alloc already pre-bakes its
address. This preserves block 0 identity (so RLP imports cleanly) while
activating PQ precompiles at the network upgrade timestamp.

Same fix pattern as mainnet C-Chain (commit ae7f530).

Unblocks: #45 Zoo mainnet RLP import cascade.
2026-06-02 03:46:49 -07:00
Hanzo AI ae7f530184 configs(mainnet): revert cchain.json to RLP-matching 2-alloc form + forward-date upgrade.json
Empirically verified 2026-06-02 via ~/work/lux/state/cmd/genesis-hash-empirical:

  Block 0 hash: 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e
  RLP parent:   0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e
  Result:       MATCH

cchain.json reverted from 1-alloc HEAD form (which produced 0x2f4ae11a — does NOT
match historical RLP) back to the canonical 2-alloc form: treasury 0x9011E888 +
warp precompile pre-alloc at 0200000000000000000000000000000000000005 with
code:0x01, nonce:1, skipPostMergeFields:true, timestamp 0x672485c2,
baseFeePerGas 0x5d21dba00, feeConfig.targetGas 500000000.

upgrade.json: all non-warp precompile activations moved from blockTimestamp:0 to
blockTimestamp:1766708400 (Dec 25 2025 16:20 PST per LP-9xxx fork). This stops
core.ApplyPrecompileActivations from running at genesis time and mutating the
state root away from 0x2d1ceda.... Only warpConfig stays at blockTimestamp:0
because the alloc already pre-bakes its address.

cchain.canonical.json: annotated documentation companion preserving genesisHash
and stateRoot fields for human readers.

Authoritative empirical study + tooling: ~/work/lux/state/LLM.md section
"RLP <-> Genesis <-> Upgrade — Canonical Migration Contract (2026-06-02, empirically verified)".

Unblocks: #131 (mainnet C-Chain RLP repro), #177 (Phase E retry), #167 cascade.
2026-06-02 03:35:50 -07:00
Hanzo AI 7a829a9d3f configs/local: ship canonical chain-configs/C/config.json (pq:true)
Localnet C-Chain runtime config. Mirrors the same shape as
~/work/lux/state/chain-configs/lux-mainnet/config.json — pq:true
strict-PQ profile enables RefuseUnderStrictPQ for classical
primitives. Discovered missing during localnet 1337 bring-up (#148).
2026-06-02 01:26:28 -07:00
Hanzo AI 2f3a1ef93e test(ParseAddress): use valid bech32 checksum for non-lux HRP case
The 'reject non-lux HRP' case had checksum cr6m3y on payload that
hashes to 537yam — i.e. invalid checksum. ParseAddress's bech32
decode rejected before the HRP check, so the assertion ('unsupported
HRP "avax"') never fired.

Use a different HRP (btc) with a valid checksum on the same 20-byte
payload so the rejection lands at the HRP whitelist, not bech32 decode.
2026-06-01 22:30:21 -07:00
Hanzo AI 0218ea44ab go.mod: bump luxfi/accel v1.1.7 → v1.1.8 (//go:embed c_api.h fresh-clone fix) 2026-06-01 22:00:06 -07:00
Hanzo AI cb9b5fd431 go.mod: bump luxfi/accel v1.1.4 → v1.1.7 (fresh-clone CI fix) 2026-06-01 21:55:46 -07:00
Hanzo AI 665889c1c7 configs(mainnet): wire dex/router admin + 3 new precompiles in upgrade.json
dexConfig now ships with an explicit protocolFeeController
(0x9011E888251AB053B7bD1cdB598Db4f9DEd94714 — the LUX_MNEMONIC-derived
treasury, KMS-controlled). This is the transitional placeholder while
the production multisig is being deployed; downstream PR will swap to
the multisig address before mainnet. The placeholder is NOT a publicly-
known Anvil/Hardhat key — it is a deterministic key derived from a
mnemonic that lives only in KMS.

precompile v0.5.28 refuses activation when dexConfig.protocolFeeController
is empty or matches a known Anvil/Foundry dev address, so this entry
MUST carry the field to bootstrap the chain. Without it luxd halts at
the activation block.

routerConfig wired with zero v2/v3 router/factory addresses (the
zero-as-skip semantics in precompile/dex/router_module.go).

Three previously-missing precompiles registered with the modules.Module
registry in precompile v0.5.28 are now gated to activate at the Quasar
Edition timestamp (1766708400 = 2025-12-25 16:20 PST):

  - kzg4844Config   — LP-3665 EIP-4844 KZG point-evaluation extensions
  - secp256r1Config — EIP-7212 passkey/WebAuthn P-256 verify
  - ed25519Config   — LP-3211 Solana/TON/XRP cross-chain signature verify

Activation timestamp 1766708400 is unchanged across the whole Quasar
block — single per-edition timestamp consistent with every other
precompile in the cohort.
2026-06-01 21:05:44 -07:00
Hanzo AI e8a04f5ca2 configs(mainnet): canonical = RLP-matching genesis; upgrades in upgrade.json
Replace configs/mainnet/cchain.json with the minimal genesis shape that
matches the historical RLP archive (= byte-equal to
~/work/lux/state/pebbledb/configs/lux-mainnet-96369/genesis.original.json,
SHA256 55114f20049fc9fa8aa4be7b5b6c4057ad9756b5718191b99cbf42bb8a512ad8).

Previously cchain.json had baked-in:
- arrowGlacierBlock, cancunTime, blobSchedule, evmTimestamp (modern EVM forks)
- Warp precompile alloc at 0200...0005 (code=0x01, nonce=0x1)
- Two-alloc shape (warp precompile + 0x9011 deployer)

These additions changed the genesis hash from the RLP-matching value to
0x3f4fa2a0..., which is why admin_importChain has been failing.

NEW shape (matches RLP, one source of truth):
- Bare EIP block activations (homestead through london at 0)
- feeConfig + warpConfig {blockTimestamp: 1750805381, quorumNumerator: 67}
- Single alloc (0x9011 deployer treasury)
- No modern fork or precompile baked into genesis

NEW configs/mainnet/upgrade.json carries forward-dated activations:
- Quasar Edition (Dec 25 2025 16:20 PST = unix 1766708400):
  42 PQ precompiles (eciesConfig excluded per task #99 as unsafe on
  public chain), cancunTimestamp = 1766708400
- Etna era (Dec 25 2024) already in effect: etnaTimestamp = 1735143600,
  warpConfig + feeConfigManagerConfig kept as before
- 42 entries = full set from hanzo-mainnet config MINUS eciesConfig

Block 0 identity preserved → RLP imports cleanly → forward-dated
Quasar Edition rules activate at runtime without changing block 0 hash.

Re-embedded cChainGenesis in configs/mainnet/genesis.json to match the
new cchain.json (jq --rawfile).

Companion docs:
- ~/work/lux/migration/LLM.md (Quasar Edition migration spec)
- ~/work/lux/state/LLM.md "RLP ↔ Genesis ↔ Upgrade Migration Contract"

CRITICAL: empirical verification of the new genesis hash against the RLP
must happen at deploy. The killed CCHAIN-LIVE agent reported the running
RLP expects 0x595e9630..., but earlier LLM.md notes (line 540) reference
0x3f4fa2a0... as the historical-RLP hash. The two diagnostics
contradict — likely indicates the RLP was regenerated at some point or
two RLP versions exist. The new cchain.json matches genesis.original.json
which lives next to the RLP archive at the state repo, so empirically it
should match the current /data/lux-mainnet-96369.rlp.
2026-06-01 20:11:01 -07:00
Hanzo AI 30501fe212 merge: decomplect/builder-security-nested-modules 2026-06-01 16:34:40 -07:00
Hanzo AI 65fe3250cd merge: chore/kill-subnet 2026-06-01 16:34:40 -07:00
Hanzo AI 9daf168bbd merge: chore/kill-fuji 2026-06-01 16:34:40 -07:00
Hanzo AI b69c33a8f8 scrub: subnet/l2 → chain (canonical vocabulary, forward-only)
Wire-format codec IDs unchanged. CLI aliases deleted; chain is the
command. No backwards-compat shims, no deprecation comments.
2026-05-31 14:49:54 -07:00
Hanzo DevandGitHub 302636b4ae Merge pull request #13 from luxfi/bootstrap-l2-v1.28.0-codec-bump
cmd/bootstrap-l2: pin luxfi/node v1.28.0 (codec-v1 wallet)
2026-05-31 02:08:35 -07:00
Hanzo AI 4d38f62b94 cmd/bootstrap-l2: pin luxfi/node v1.27.24 → v1.28.0 (codec-v1 wallet)
Pre-v1.28.0 nodes wrote P-chain UTXOs at wire-version=0. v1.28.0
ships the multi-version codec (luxfi/node@c589251a1d) and writes
all new UTXOs at wire-version=1 by default. The bootstrap-l2 tool
embeds the luxfi/node wallet to build/sign CreateNetworkTx and
CreateChainTx; the v1.27.24 pin's wallet codec only knew v0, so
syncing the wallet against a fresh v1.28.0 node failed with:

  wallet sync: unknown codec version

Bump cmd/go.mod to luxfi/node v1.28.0, which pulls in the v0+v1
codec register and decodes a v1.28.0 node's UTXOs correctly.

Verified on lux-devnet: rebuilt bootstrap-l2 then ran:
  /work/bootstrap-l2 -uri=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650 \
    -hrp=dev -bip44-idx=5 -track-chain-ids=hanzo,zoo,pars,spc \
    -configs-dir=/work/configs -chain-settle-delay=180s -probe-timeout=1800s

All 4 L2 EVMs bootstrapped:
  hanzo → i3AxuEfLAqzAvabgpYvXC7QCvqXnJAHr3AYiVzfK2FDauyJSa
  zoo   → 5YP3iV6ytRjtscF8dcXWQTqd8dwjAaoZnW6oNVfDM3d2mwwvS
  pars  → JxDPdhGde9PSrZ3nLwe7shwZaXEFBoAJ8V9rGqbcKXy9FEQEm
  spc   → LHU4rNBLr1Zi2pNJXL47djkCmmyDSgTXmaB4oNQBYyGfqPtg9

Transitive bumps:
  github.com/luxfi/consensus v1.25.0 → v1.25.2
  github.com/luxfi/genesis    v1.12.15 → v1.12.19
2026-05-31 02:08:17 -07:00
Hanzo DevandGitHub 0c52be9d9b decomplect: move heavy-dep tests + nest builder/security modules (#12)
This is the third decomplect layer:

1. builder/ now a nested module (github.com/luxfi/genesis/builder)
   — uses luxfi/utxo, vm, database, proto
2. pkg/genesis/security/ now a nested module
   — uses luxfi/consensus for ChainSecurityProfile verification
3. do_transfer_test.go moved from pkg/genesis/ → builder/
   — it imports luxfi/utxo/secp256k1fx which was forcing the entire
   v1.27.x-era dep cascade onto downstream consumers via test-deps
   in the module graph

Result on the root module's MVS-visible graph:
- consensus      v1.25.0  → v1.22.84  (matches luxd v1.23.42 family)
- database       v1.18.3  → v1.17.44  (matches luxd v1.23.42 exactly)
- validators     v1.2.0   → v1.0.0    (matches luxd v1.23.42 exactly)
- threshold      v1.6.17  → GONE      (no longer in graph at all)
- node           v1.27.24 → v1.20.3   (transitive, harmless)

Closes the v1.23.x backport blocker for canonical evmAddr/utxoAddr.

luxd v1.23.43 (genesis bump) can now adopt the canonical genesis
schema without dragging the v1.27.x post-quantum threshold refactor
into a v1.23.x production line.

One-direction module dep:
- github.com/luxfi/genesis          (data types — minimal deps)
- github.com/luxfi/genesis/security (verification — uses consensus)
- github.com/luxfi/genesis/builder  (tx-building — uses utxo/vm/db)
- github.com/luxfi/genesis/cmd      (tools — uses node)

Never the reverse.
2026-05-30 17:38:21 -07:00
Hanzo AI 5f73f2b116 decomplect: move heavy-dep tests + nest builder/security modules
This is the third decomplect layer:

1. builder/ now a nested module (github.com/luxfi/genesis/builder)
   — uses luxfi/utxo, vm, database, proto
2. pkg/genesis/security/ now a nested module
   — uses luxfi/consensus for ChainSecurityProfile verification
3. do_transfer_test.go moved from pkg/genesis/ → builder/
   — it imports luxfi/utxo/secp256k1fx which was forcing the entire
   v1.27.x-era dep cascade onto downstream consumers via test-deps
   in the module graph

Result on the root module's MVS-visible graph:
- consensus      v1.25.0  → v1.22.84  (matches luxd v1.23.42 family)
- database       v1.18.3  → v1.17.44  (matches luxd v1.23.42 exactly)
- validators     v1.2.0   → v1.0.0    (matches luxd v1.23.42 exactly)
- threshold      v1.6.17  → GONE      (no longer in graph at all)
- node           v1.27.24 → v1.20.3   (transitive, harmless)

Closes the v1.23.x backport blocker for canonical evmAddr/utxoAddr.

luxd v1.23.43 (genesis bump) can now adopt the canonical genesis
schema without dragging the v1.27.x post-quantum threshold refactor
into a v1.23.x production line.

One-direction module dep:
- github.com/luxfi/genesis          (data types — minimal deps)
- github.com/luxfi/genesis/security (verification — uses consensus)
- github.com/luxfi/genesis/builder  (tx-building — uses utxo/vm/db)
- github.com/luxfi/genesis/cmd      (tools — uses node)

Never the reverse.
2026-05-30 17:38:01 -07:00
Hanzo DevandGitHub 0f6bec3e2f decomplect: split cmd/ to nested module — root has no luxfi/node (#11)
* decomplect: move SecurityProfile Resolve to pkg/genesis/security

pkg/genesis core types (SecurityProfile struct) stay free of
luxfi/consensus dep. The verification gate (ResolveProfile + hash
comparison) moves to pkg/genesis/security which imports both.

Result: downstream consumers that only need to parse canonical
genesis data (luxd v1.23.x line, tools, indexers) can do so without
dragging luxfi/consensus + luxfi/threshold + luxfi/validators
(Corona→Corona refactor) into their go.mod.

go list -deps ./pkg/genesis → no luxfi/consensus.
go test ./pkg/genesis/security/ → all 5 F102 verification tests pass.

Closes the dep cascade that blocked the v1.23.x backport of
evmAddr/utxoAddr canonical genesis. One and one way only:
data lives in pkg/genesis, verification lives in pkg/genesis/security,
consensus consumes both — never the reverse.

Closes #93 Phase 1.

* decomplect: split cmd/ into nested module (no luxfi/node in root go.mod)

The Phase 1 split (SecurityProfile struct in core, Resolve in
pkg/genesis/security subpackage) addressed package-level dep cycles,
but the genesis root go.mod still required luxfi/node v1.27.24
because cmd/bootstrap-l2 + cmd/derive100 import node directly for
wallet/tx-builder bits.

This commit moves cmd/ to a nested module
(github.com/luxfi/genesis/cmd) with its own go.mod that holds the
luxfi/node dep. Root go.mod no longer requires luxfi/node, breaking
the MVS cascade that was forcing luxd v1.23.x consumers to upgrade
28 packages just to bump genesis.

Result:
- go list -m all on root no longer surfaces luxfi/node directly
  (still transitively visible at v1.23.x-compatible versions, not the
  v1.27.x bloat)
- cmd/ retains the same source layout; tools still build via cmd/go.mod
- pkg/genesis core API unchanged
- pkg/genesis/security gate unchanged

Two-module separation:
- github.com/luxfi/genesis        — data types + verification primitives
- github.com/luxfi/genesis/cmd    — tools that need luxfi/node

One direction, no reverse import.

Closes the structural blocker for v1.23.x backport of canonical
evmAddr/utxoAddr.
2026-05-30 17:28:13 -07:00
Hanzo DevandGitHub d00198590e refactor: XAssetID → UTXOAssetID (genesis builder + bootstrap-l2) (#10) 2026-05-30 14:34:07 -07:00
Hanzo DevandGitHub 514e996737 refactor: XAssetID → UTXOAssetID (genesis builder + bootstrap-l2) (#9) 2026-05-30 14:30:59 -07:00
Hanzo DevandGitHub 57daf793e1 canonical evmAddr/utxoAddr only — drop legacy luxAddr/ethAddr shim (#8)
Per "one and one way only" — AllocationJSON now emits and accepts ONLY
the canonical evmAddr/utxoAddr field pair. The legacy luxAddr/ethAddr
aliases that were dual-emitted to keep luxd v1.23.x happy are gone.

Consumers of this package (luxd, genesis tool, gen-localnet) must be on
a build that reads the canonical names. Coordinated with luxd v1.23.43
which already renamed Allocation.{LUXAddr,ETHAddr} → {UTXOAddr,EVMAddr}
on main / chore/kill-fuji.

Devnet genesis: drop 3 ghost InitialStakers (LnEy5o1NPzpmSy56uavuoMDL9oRcMjHjs,
Pubc76XYx1BQCqZreJUUoAaXgmmEzapSz, N5DAqzfZpSqkthaCtdfgkyx7m8ERut16A) so
8-staker genesis matches 5-pod sybil quorum. Closes P-chain ratification
stall at numValidators=4 observed during devnet bootstrap.

Tests: pkg/genesis, configs, builder, cmd/bootstrap-l2 all pass.
2026-05-30 09:21:04 -07:00
Hanzo AI 379ff773e5 canonical evmAddr/utxoAddr only — drop legacy luxAddr/ethAddr shim
Per "one and one way only" — AllocationJSON now emits and accepts ONLY
the canonical evmAddr/utxoAddr field pair. The legacy luxAddr/ethAddr
aliases that were dual-emitted to keep luxd v1.23.x happy are gone.

Consumers of this package (luxd, genesis tool, gen-localnet) must be on
a build that reads the canonical names. Coordinated with luxd v1.23.43
which already renamed Allocation.{LUXAddr,ETHAddr} → {UTXOAddr,EVMAddr}
on main / chore/kill-fuji.

Devnet genesis: drop 3 ghost InitialStakers (LnEy5o1NPzpmSy56uavuoMDL9oRcMjHjs,
Pubc76XYx1BQCqZreJUUoAaXgmmEzapSz, N5DAqzfZpSqkthaCtdfgkyx7m8ERut16A) so
8-staker genesis matches 5-pod sybil quorum. Closes P-chain ratification
stall at numValidators=4 observed during devnet bootstrap.

Tests: pkg/genesis, configs, builder, cmd/bootstrap-l2 all pass.
2026-05-30 09:20:44 -07:00
Hanzo DevandGitHub 08e9e6904e chore: update 2026-05-29 23:33:13 -07:00
Hanzo AI 144cfed249 chore: update 2026-05-29 23:29:11 -07:00
Hanzo DevandGitHub 770c12f3ab chore(genesis): kill subnet — rename to chainID across genesis tool surface (#6)
Per CLAUDE.md "translate upstream Lux's 'subnet' out immediately":
zero remaining `subnet|Subnet|SUBNET` occurrences in lux/genesis Go +
JSON files. All eleven were comment-level cruft or one struct field
that hadn't been renamed yet.

## Changes

`cmd/bootstrap-l2/main.go`:
- Top-of-file vocabulary block updated — kills the "upstream
  CreateSubnetTx" + "ConvertSubnetToL1Tx" framing in favor of the
  canonical three-name trinity: `chainID` (chain-owner network),
  `blockchainID` (blockchain's own ID), `evmChainID` (EIP-155).
- `platformBlockchain.SubnetID` → `ChainID`; JSON tag
  `"subnetID"` → `"chainID"`.
- `resultChain.ChainOwnerID` → `ChainID`; JSON tag
  `"chainOwnerId"` → `"chainId"`.
- Inline struct in `existingByName` map: `ChainOwnerID` →
  `ChainID`, log line reads `chainID=` instead of `ownerID=`.
- All callsites updated.

`cmd/genesis/main.go`, `pkg/genesis/keys.go`,
`pkg/genesis/allocations.go`:
- Four comment-level rephrases: "subnet-bootstrap CLI" → "chain-
  bootstrap CLI", "subnet creation operations" → "chain creation
  operations".

## Output JSON wire change

The bootstrap result file emitted by `--output` now uses
`{chainId, blockchainId, evmChainId}` per chain instead of
`{chainOwnerId, blockchainId, evmChainId}`. Downstream consumers
(universe operator, chainboot, anything that reads
devnet-l2-live.json) MUST update their parsers — this is a
breaking wire change in the file format, intentional.

The platform.getBlockchains JSON-RPC method's wire format stays
upstream-shaped on the way IN; our parser now decodes from
`"chainID"` (post-rename in lux/node `APIBlockchain`). If running
against an older lqd that still emits `"subnetID"`, the field
silently zero-values — which is the correct safety behavior (the
caller will fall through to the create path and the operator gets
a re-create-attempt failure rather than a silent ghost reuse).

## Verified clean

  grep -rnE "subnet|Subnet|SUBNET" --include="*.go" --include="*.json"
  # zero matches in lux/genesis

Build clean. 9 normalizeAllocKey tests still pass.
2026-05-29 21:07:57 -07:00
Hanzo DevandGitHub 45008b7205 fix(genesis): canonical 0x prefix on all devnet alloc keys + fail-loud bootstrap-l2 (#72) (#5)
* feat(genesis): bootstrap-l2 — durable replacement for /tmp script

Recreates the v1.27.x ConvertSubnetToL1 / CreateChain pipeline previously
maintained as /tmp/bootstrap-l2-v127. New tool at cmd/bootstrap-l2/:

- Reads MNEMONIC from env, derives BIP44 m/44'/9000'/0'/0/<idx>
- For each chain in --chains: IssueCreateNetworkTx → wallet re-sync →
  IssueCreateChainTx (vmID nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr)
  → IssueAddChainValidatorTx for every primary validator
- Probes info.isBootstrapped(chain=<bcID>) + eth_blockNumber>0 per chain;
  if either misses the deadline the whole tool exits non-zero (no partial
  state propagated to YAML/CMs)
- Emits a single JSON document {chains:[{name, subnetId, blockchainId,
  evmChainId, firstBlockHex, bootstrappedAt}, ...]} for downstream
  YAML/CM updates
- --print-addr-only mode to verify funding before spending

go.mod: bump luxfi/node v1.23.36 → v1.27.21 to match cluster runtime
(devnet runs luxd v1.27.21; pre-rename wallet codecs would produce txs the
nodes can't decode). Only existing genesis use of luxfi/node is the
address formatter — no behavior change for other cmds.

Designed for the four canonical Lux L2 EVMs (hanzo, zoo, pars, spc)
but the chain list is data-driven via --chains and --configs-dir.

* feat(genesis): bootstrap-l2 default VM ID → mgj786NP... (native subnet-evm)

Devnet luxd-0 /data/plugins contains exactly two subnet-evm plugin IDs
(mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 and
ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE), neither matching the
brand-namespaced alias nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr
recorded in the 2026-05-27 chain-aliases ConfigMap annotation.

That alias required a runtime symlink (created post-startup, lost on PVC
remount) — the same fragile path that's been the historical source of
L2-EVM bootstrap failures. Mainnet's lux-mainnet/luxd-startup.yaml uses
mgj786NP... directly and `cp`'s it as the ag3GR... duplicate; we mirror
that pattern here so the plugin is *always* present without a startup
script writing into a PVC.

The chain-aliases CM annotation about nyGCobir... is now stale and will be
rewritten in the post-bootstrap update.

* feat(genesis): bootstrap-l2 — --existing-subnet-ids to resume after partial bootstrap

When CreateNetworkTx succeeds but the subsequent CreateChainTx fails (or
the tool gets killed mid-flight), the subnet still exists on-chain and
the fee was burned. Re-running the tool without --existing-subnet-ids
would burn another CreateNetwork fee.

Format: --existing-subnet-ids=hanzo:2PkWqv...,zoo:abc...

For each named chain, skip IssueCreateNetworkTx and pass the supplied
subnet ID straight into IssueCreateChainTx. Chains not in the map
continue to create a fresh subnet.

Also surfaces a real operational pattern: load-balanced services like
svc/luxd-headless can pin a kubectl port-forward to a lagging pod whose
state is behind the chain head; the tool must still complete by
re-using the subnet visible on the leader pod.

* feat(genesis): bootstrap-l2 — EVM heartbeat tx + decoupled bootstrap probe

Acceptance criterion 'eth_blockNumber > 0' on a fresh subnet-evm chain
requires injecting the first tx (subnet-evm only seals blocks when a tx
arrives). Adding two new flags:

  --evm-heartbeat-key=<hex>   LUX_PRIVATE_KEY hex (0x9011E888...).
                               Sends a 0-value self-tx after the chain
                               reports info.isBootstrapped=true; the first
                               tx rolls block 1.

  --probe-bootstrap-only      Skip the eth_blockNumber>0 wait. Used when
                               an out-of-band heartbeat (chain-heartbeat
                               CronJob) will roll block 1 later.

The probe is also split into waitBootstrap + probeChain so the heartbeat
fires after isBootstrapped but before eth_blockNumber checks (otherwise
eth_sendRawTransaction returns 'chain not bootstrapped').

Signing path: luxfi/crypto/secp256k1 → ecdsa.PrivateKey → geth/core/types
SignTx with EIP-155 signer scoped to each chain's evmChainID. EVM address
derivation goes through luxfi/crypto/secp256k1.PubkeyToAddress so we never
import an upstream go-ethereum crypto package.

* feat(genesis): bootstrap-l2 — preset chain ID (chain:subnetID:chainID) for resume

Extends --existing-subnet-ids parser to accept an optional third field
<chainID>. When present, both IssueCreateNetworkTx and IssueCreateChainTx
are skipped; the tool only waits for isBootstrapped, sends the heartbeat
(if --evm-heartbeat-key is set), and probes eth_blockNumber.

This is the post-partial-failure resume pattern: if hanzo's subnet+chain
were created successfully in a prior run but zoo onwards failed (or were
never attempted), pass

  --existing-subnet-ids=hanzo:2PkWqv...:rHiiTB...,zoo:abc...

and the tool will skip the hanzo P-chain spend, treat hanzo as already
created, and continue with the next chain.

Also gates the AddChainValidatorTx and post-chain wallet re-sync on
presetChainID being unset, since both are unnecessary in resume mode.

* fix(genesis): canonical 0x prefix on all devnet alloc keys + fail-loud bootstrap-l2 (#72)

Two-part fix for the alloc-key shape that bootstrap-l2 consumes:

## 1. Genesis configs patched at source (4 files)

Every alloc key in:
  - configs/hanzo-devnet/genesis.json
  - configs/pars-devnet/genesis.json
  - configs/spc-devnet/genesis.json
  - configs/zoo-devnet/genesis.json

now matches the canonical /^0x[0-9a-fA-F]{40}$/ shape. The EVM
genesis loader rejects unprefixed keys; previously bootstrap-l2's
in-memory repair was masking the issue. Fixing at source makes the
configs self-describing and stops the "works for me but won't load
on a fresh node" failure mode.

## 2. bootstrap-l2 normalization is now fail-loud (cmd/bootstrap-l2/main.go)

Pre-flight repair upgrades from silent fix to:

  - Normalize 0X → 0x (preserve body case for EIP-55 checksum signal)
  - Add missing 0x prefix (repair, log count)
  - Reject anything that doesn't match /^0x[0-9a-fA-F]{40}$/ AFTER
    repair via log.Fatalf with up to 5 sample keys

Per the user's directive:
  > "normalize alloc keys on load/write or fail loudly before chain
  >  creation. I prefer normalization plus a validation log, not
  >  silent mutation with no signal."

Log line now shows both healthy and repaired counts:
  [devnet] hanzo: alloc keys ok=2 0x-repaired=0 (in-memory only, file unchanged)

A genuinely malformed key (non-hex, wrong length) aborts the entire
bootstrap before any CreateNetworkTx burns LUX:
  [devnet] hanzo: 1 malformed alloc keys (require canonical
                   /^0x[0-9a-fA-F]{40}$/); sample: ["0xZZZ..."]

## 3. Single source of truth: normalizeAllocKey() helper

New pure function returns `(canonical, repaired, valid)`. Every
consumer (the in-memory repair, future validation tooling, any
on-disk rewriter) routes through this one function. Routes:

  - body-case PRESERVED (EIP-55 checksum)
  - prefix-case NORMALIZED (`0X` → `0x`)
  - shape-validated (length 40, hex only)
  - idempotent

## Tests

9 new tests in cmd/bootstrap-l2/main_test.go:

  - already-canonical roundtrip
  - missing-prefix repair flag
  - 0X-prefix normalization
  - body-case preservation (EIP-55 invariant)
  - short body rejection (39/38/empty)
  - long body rejection (41)
  - non-hex rejection (g, _, space)
  - empty string rejection
  - idempotency under repeated normalize calls

All pass. Build clean.
2026-05-29 20:02:37 -07:00
Hanzo AI 1bf501799b feat(genesis): bake all upgrade timestamps + 43 canonical precompiles
Genesis files now self-describe the network's full capability surface.
No more CR-level chainUpgradeConfig overrides needed at runtime; if you
boot luxd with --genesis-file=<any of these>, you get Etna/Durango/
Fortuna/Granite + 43 precompiles automatically.

Activated upgrade timestamps (all set to 0 — active from genesis):
  evmTimestamp, durangoTimestamp, etnaTimestamp, fortunaTimestamp,
  graniteTimestamp

Activated precompiles (43, all at blockTimestamp 0 — sourced from
ConfigKey constants in ~/work/lux/precompile/*/module.go):
  ai, anchor, attestation, babyjubjub, blake3,
  bls12381 (G1Add, G1Mul, G1MSM, G2Add, G2Mul, G2MSM, Pairing),
  bridgeRegistrar, cggmp21, computeMarket, coronaThreshold (renamed
  from coronaThreshold), curve25519, dex, ecies, fhe, fixedPointMath,
  frost, graph, hpke, hqcEncapsulate, magnetar, mldsa, mlkem,
  p3q, pasta, pedersen, poseidon, pqcrypto, pulsar, ring, router,
  slhdsa, sr25519, stableSwap, vrf, x25519, xwing, zk

WARP config: blockTimestamp=0, quorumNumerator=67,
requirePrimaryNetworkSigners=false (sovereign signers — works pre-
and post-L1-conversion).

Applied to all 15 chain genesis files: C/hanzo/zoo/pars/spc on
mainnet/testnet/devnet. Each produces a deterministic genesis hash.
2026-05-27 01:35:15 -07:00
Hanzo AI 41cfd7827a fix(genesis): pars 7B LUX (chainId 7070), spc 11B LUX (chainId 36911)
Previous commit ca7637c incorrectly applied hanzo/zoo's 2T LUX template.
Canonical amounts per ~/work/pars/genesis/networks/mainnet/genesis.json
(7B confirmed) and product spec (spc=11B). Treasury still 0x9011E...
(LUX_MNEMONIC m/44'/9000'/0'/0/5 derived).

Re-bootstrapped on mainnet 2026-05-25 with corrected genesis:
  pars: 2M4yxnBoLi... validator-set, 2wYWjBHziT... blockchain ID (parsv4)
  spc:  2LN6THJWF1... validator-set, 2YTuJW9b1k... blockchain ID (spcv4)
2026-05-25 23:13:30 -07:00
Hanzo AI ca7637ca9a fix(genesis): pars/spc mainnet → LUX_MNEMONIC-funded treasury (0x9011E...)
Both pars-mainnet and spc-mainnet shipped with placeholder allocs:
  - pars-mainnet: chainId 494949 in file but on-chain reports 0x1b9e (7070);
    treasury was 0x12c6EE1d... w/ 16M LUX, not LUX_MNEMONIC.
  - spc-mainnet: chainId 36911 (matches), but treasury 0x12c6EE1d...
    w/ 16M LUX — also not LUX_MNEMONIC.

Both block 0 hashes are identical (0x4dc9fd5cf4...) — they were
bootstrapped from the same stub genesis.

Fix mirrors hanzo-mainnet pattern: alloc 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
(LUX_MNEMONIC treasury, derives from K8s secret lux-deployer.LUX_PRIVATE_KEY)
with 0x193e5939a08ce9dbd480000000 wei balance.

Re-bootstrap required on mainnet — both chains are at block 0x0, no
state loss. New blockchain IDs will be issued via CreateChainTx; the
chain aliases 'pars' and 'spc' will repoint via chain-aliases ConfigMap.
2026-05-25 22:56:32 -07:00
Hanzo AI 4a0f19e47e chore: update 2026-05-25 15:13:09 -07:00
Hanzo AI 4b1cdad100 keys: fix call sites — keccakBytes wraps Sum→slice properly 2026-05-23 16:27:05 -07:00
Hanzo AI 66ddf3ca26 keys: keccakBytes helper — Go disallows slicing function-returned arrays 2026-05-23 16:25:29 -07:00
Hanzo AI f6ba5aef36 consume crypto/keccak256 (was crypto/keccak) — bump crypto v1.19.14 2026-05-23 16:21:45 -07:00
Hanzo AI 8ef8e731d4 keys: use crypto/keccak directly — drop crypto.PubkeyToAddress wrapper
luxfi/crypto/keccak is the canonical Keccak-256 subpackage (parallel to
secp256k1). Replaces:

  crypto.PubkeyToAddress(pub.PublicKey)
    →
  keccakAddr(pub.PublicKey.X, pub.PublicKey.Y)

where keccakAddr is a 5-line helper that says exactly what the EVM
address derivation is:

  buf := make([]byte, 64)
  pub.X.FillBytes(buf[:32])  // big-endian, zero-padded
  pub.Y.FillBytes(buf[32:])
  h := keccak.Sum256(buf)
  return h[12:]

The local keccak256(...) helper now routes through keccak.Sum256 (one
hashing path, no sha3 import for that primitive).
2026-05-22 21:19:55 -07:00
Hanzo AI 28e520ef7e decomplect: drop evmcrypto/utxocrypto aliases — use crypto + secp256k1 by name
dropping aliases on github.com/luxfi/crypto and github.com/luxfi/crypto/secp256k1.
no name collisions with stdlib (validator_keys.go's crypto/rand is rand, not crypto).
also purges remaining ethAddr / luxAddr / luxKey local vars (renamed to addr /
utxoAddr / secpKey).
2026-05-22 21:05:17 -07:00
Hanzo AI c114107bac purge remaining eth* identifiers — single canonical EVMAddr/UTXOAddr
ParseETHAddress → ParseEVMAddress
  ValidatorKeyInfo.EthAddr → .EVMAddr
  local ethAddr / EthAddr vars → evmAddr / EVMAddr
  ethPrivKey / ethShortID / ethAddrStr → evmPrivKey / evmShortID / evmAddrStr
  privateKeyToETHAddress → privateKeyToEVMAddress
  comments: "Get ETH address" → "Get EVM address", "Ethereum address" → "EVM address (H160 hex)"
  test error strings + test loop vars also updated for consistency

The only ETH/Ethereum reference remaining is the upstream `ethcrypto`
import alias (github.com/luxfi/crypto, aliased ethcrypto) — that's an
external package name, kept as-is.
2026-05-22 20:56:48 -07:00
Hanzo AI 462fa1a206 drop ethAddr/luxAddr string tags — canonical is evmAddr/utxoAddr 2026-05-22 20:40:50 -07:00
Hanzo AI 8948b4e1cb EVMAddr (Go) + UTXOAddr (Go) — pure chain-model naming, no eth/lux branding
Renames Go struct field ETHAddr → EVMAddr across types.go, keys.go,
allocations.go, validator_keys.go, config.go, builder/builder.go,
cmd/genesis, cmd/checkkeys. JSON tags stayed evmAddr already (from
v1.12.6). Result: matched pair EVMAddr+UTXOAddr on both Go and JSON
sides.

Kept ParseETHAddress as the input-parsing function name — it describes
what it parses (an Ethereum-format hex address), not the field it
populates (an EVMAddr).

LLM.md updated to state the canonical pair plainly; no rename history.
2026-05-22 20:37:30 -07:00
Hanzo AI 2a829c0af2 JSON tags: ethAddr → evmAddr (chain-model name, paired with utxoAddr)
Go field stays ETHAddr (names the H160 byte format, which IS Ethereum's
address spec — we're wire-compatible, not branded). JSON tag changes to
evmAddr so the user-facing pair (evmAddr / utxoAddr) describes our chain
models symmetrically without branding us as Ethereum.

This is the hybrid that fell out of the linter pass: Go identifier =
format-name, JSON tag = chain-model-name. Documented in LLM.md so we
don't regress.

Touches: pkg/genesis/{types,keys,allocations,validator_keys,config}.go,
configs/*/genesis.json + pchain.json + getgenesis_test.go,
scripts/gen-localnet.py. Tests green.
2026-05-22 20:10:48 -07:00
Hanzo AI 11e221b5ce configs/devnet: align wallets to 100M/wallet — 50M initialAmount + 50M unlock@0 (matches testnet) 2026-05-22 00:06:43 -07:00
Hanzo AI 28d59d9dcc docs: refresh testnet+devnet C-Chain hashes to current canonical
Live deployed hashes verified byte-equal to current canonical cchain.json:
  testnet: 0xfc909f7e992d9cb91485f114f6d333f3823a12f2c72bbf51ed2c8eea749b2d2e
  devnet:  0x836f6053473e4331bb347afc45b641f12075c63a302f4e56e64239a3ba4acd4b

Previous values were from before commits ae1c52b (Shanghai/Cancun/Prague
at genesis) and 88b6561 (gasLimit 1B). testnet+devnet were both "block 0
fresh" with no committed historical state, so the hash update is in-spec.

Mainnet hash unchanged — its cChainGenesis is locked by the 1.08M-block
historical chain.
2026-05-21 18:43:51 -07:00
Hanzo AI 8bf3304490 configs/devnet: bump wallet unlock amount 10M → 50M to match testnet
devnet/genesis.json (the bundled blob mounted into pod ConfigMaps)
was stale relative to devnet/pchain.json — sharded source had 50M,
bundled blob had 10M. testnet/genesis.json already had 50M.

Surgical sed substitution on the wallet unlock amount; validator
stake allocations (3 unlock periods at 1B nLUX each) untouched.
2026-05-21 18:02:11 -07:00
Hanzo AI 9eed1b8791 no backcompat — canonical address fields are ethAddr + utxoAddr
Renames Allocation.EVMAddr → ETHAddr (json tag `ethAddr`) and
KeyInfo.EVMAddr → ETHAddr across pkg/genesis, builder, cmd/genesis,
cmd/checkkeys. AllocationJSON drops the legacy `evmAddr` tag entirely
— there is no aliasing, no UnmarshalJSON normalizer, no fallback.
JSON files and Go code share one canonical pair: ethAddr + utxoAddr.

configs/getgenesis_test.go updated to assert against the canonical
field. parseAllocations error message says "invalid eth address"
to match the new identifier.

Tests:
  ok  github.com/luxfi/genesis/builder
  ok  github.com/luxfi/genesis/configs
  ok  github.com/luxfi/genesis/pkg/genesis
2026-05-21 12:51:40 -07:00
Hanzo AI 526ce8b641 kill F12/F30 nid-hardened derivation paths (no slop)
Removed:
- LoadKeysFromMnemonic's `nid uint32` parameter (was ignored)
- BuildWalletAllocations (replaced by canonical BuildBIP44WalletAllocations)
- BuildWalletKeyHex (no callers after removing the nid-hardened scheme)
- deriveLuxAccount helper (m/44'/9000'/nid'/...) — only used by removed code
- genesis_hd_branches_test.go entire file (tested abandoned NID-hardening
  requirement; canonical Avalanche/Lux BIP44 is intentionally nid-stable
  so the same mnemonic gives the same addresses in every wallet)

TestTransferPChain now skips by default — opts in via LUX_LIVE_TESTS=1
(it hits a live api.lux-dev.network endpoint, not appropriate for unit-test
runs that fail when CF returns 522).

cmd/checkkeys/main.go: dropped unused NETWORK_ID env handling.
cmd/genesis/main.go: BuildWalletAllocations → BuildBIP44WalletAllocations.

All tests pass in clean offline mode now:
  configs    OK (TestPrimaryChainShards_PerChainCanonicalChainID green)
  pkg/genesis OK
  builder    OK
2026-05-21 12:30:15 -07:00
Hanzo AI 8d127d7d50 drop accidental local-backup.json 2026-05-21 10:43:50 -07:00
Hanzo AI 614a994402 kill all legacy/backward-compat surfaces
- types.go: drop AllocationJSON.UnmarshalJSON legacy alias remapping;
  drop UnparsedAllocation/UnparsedStaker type aliases; drop
  formatBech32WithChain deprecated helper.
- configs: strip ethAddr/luxAddr/avaxAddr/xAddr legacy fields from
  every shipped JSON shard; rename to canonical evmAddr/utxoAddr only.
- configs: add top-level chainId field to every letter-chain shard
  (mainnet/testnet/devnet/localnet — the test expected this).
- localnet/dev: rebuild allocations to canonical 50M LUX per account
  on X + 50M LUX per account on P (matches mainnet/testnet/devnet).
- tests: TestFormatBech32WithChain → TestChainPrefixFormat_KnownVectors,
  TestGetGenesisLocalnet expects 50M not 10M per allocation,
  field references updated to evmAddr/utxoAddr.

One-and-only-one-way enforced. No deprecated aliases. New code MUST use
EVMAddr/UTXOAddr, ChainPrefix.Format, UTXOAssetIDFor.
2026-05-21 10:43:42 -07:00
Hanzo AI 4daaa65505 configs(mainnet,testnet): add xchain.json shards (keep X-Chain baked)
Regenerate mainnet/testnet genesis.json via GetGenesis() so the merged
output now embeds xChainGenesis (the small asset descriptor shard
{"symbol":"LUX","name":"Lux","denomination":9}) alongside the existing
cChainGenesis and the seven letter-chain shards (a/b/d/g/k/q/t/z).

The per-network xchain.json shard files already existed; the on-disk
genesis.json snapshots had drifted (predated the post-decomplect
shard schema) so the xChainGenesis field was missing on mainnet and
testnet. Devnet and localnet already had it baked.

Why this matters: luxd v1.27+ X-Chain-optional builder treats absent
xChainGenesis as "skip X-Chain bake". Every existing wallet and
indexer expects X-Chain to be live on mainnet/testnet, so the shard
must remain baked.

Verification:
  $ jq -r '.xChainGenesis' configs/{mainnet,testnet}/genesis.json
  {"symbol":"LUX","name":"Lux","denomination":9}
  {"symbol":"LUX","name":"Lux","denomination":9}

Tests: TestGetGenesis_XChainShardPresentEmbedsXChainGenesis and
TestGetGenesis_AllPrimaryChainsBakedIn pass for {mainnet,testnet,
devnet,localnet}; builder/* unchanged and green.
2026-05-21 03:45:02 -07:00
Hanzo AI 419cea1b43 pkg/genesis: ChainPrefix type — decomplect chain prefix from HRP
formatBech32WithChain("P", hrp, addr) braided chain identity into bech32
formatting at every call site. Chain prefix (P-/X-) is per-chain metadata;
HRP (lux/test/dev/local) is a per-network parameter — they should be
orthogonal.

Introduce ChainPrefix as a named string type with PChainPrefix / XChainPrefix
constants and a Format(hrp, addr) method that delegates to luxfi/address.
Call sites compose by method invocation: PChainPrefix.Format(hrp, addr[:]).

The deprecated formatBech32WithChain is kept as a thin alias for any
remaining caller; output bytes are identical to the pre-decomplect form.
2026-05-21 03:36:41 -07:00
Hanzo AI 97aa05c4da pkg/genesis: ParseAddress rejects non-lux HRPs (avaxAddr footgun) 2026-05-21 03:34:15 -07:00
Hanzo AI 09558bb2e3 rename ETHAddr→EVMAddr, LUXAddr→UTXOAddr (uniform UTXO/EVM split)
Address types now reflect the actual two-address model that exists across
all chains:
  - EVMAddr (0x H160) — C-Chain and every other EVM chain
  - UTXOAddr (bech32, P-/X- prefix interchangeable) — P-Chain and X-Chain

The old names (ETHAddr / LUXAddr / avaxAddr / xAddr) confused two
orthogonal axes (Ethereum-vs-Lux brand × address-encoding-type) with
each other. UTXO/EVM is the actual technical distinction.

Backward compat:
  - JSON: AllocationJSON.UnmarshalJSON accepts evmAddr/ethAddr +
    utxoAddr/xAddr/luxAddr/avaxAddr (legacy upstream Avalanche).
  - Existing config files: bumped with both new + legacy fields present.

Builds + tests green on luxfi/genesis and luxfi/node/genesis/builder.

Also: each allocation gets 50M LUX on X (initialAmount) + 50M LUX on P
(unlockSchedule locktime=0). For 1000 accounts: 50B on each chain →
100B total per env across P+X. mainnet legacy retained at-is (1.08M
historic blocks reference original asset IDs).
2026-05-20 16:10:50 -07:00
Hanzo AI 1fdd56807b fix(testnet,devnet/pchain): migrate allocations from initialAmount-only to unlockSchedule
luxd v1.23.31 builder.go only iterates `unlockSchedule` to build P-Chain
platform allocations. Allocations with only `initialAmount` (and
`unlockSchedule: null`) are silently dropped — supply gets counted but
no UTXOs materialize at the addresses. This blocks any P-Chain operation
(staking, subnet creation, token transfers) on testnet/devnet because
the canonical deployer addresses report balance=0 despite genesis claim.

Mainnet works around this because every allocation has explicit
unlockSchedule entries (vesting locks). testnet/devnet had compact
"initialAmount: 10M, unlockSchedule: null" entries.

Fix: migrate 995 testnet + 995 devnet allocations to use
`{initialAmount: 0, unlockSchedule: [{amount: 10M, locktime: 0}]}`.
Same total amount, immediately unlocked at locktime=0, now properly
loaded into platformAllocations by the builder.

Also fixed initialStakedFunds to point at allocations[0..4].luxAddr
(the BIP44 m/44'/9000'/0'/0/{0..4} derivation) instead of the validator
reward addresses, matching the working mainnet pattern.

Verified: testnet/devnet alloc[10] now shows 10M LUX queryable balance;
4 canonical subnets deployed successfully on each (zoo 200201/200202,
hanzo 36962/36964, spc 36910/36912, pars 494950/494951).
2026-05-20 15:06:13 -07:00