210 Commits
Author SHA1 Message Date
Hanzo DevandGitHub 75bf428b7b chore: bump Go toolchain to 1.26.3 (#3)
Pin Go version to 1.26.3 across go.mod, CI workflows, and Dockerfiles
for canonical alignment with the rest of the luxfi/* stack.
2026-05-16 16:43:14 -07:00
Hanzo DevandGitHub f74398401b Merge pull request #2 from luxfi/licensing/canonical-pointer
docs: add LICENSING.md pointer to canonical Lux IP strategy
2026-05-15 16:35:32 -07:00
Hanzo Dev 5613b47d7f docs: add LICENSING.md pointing at canonical Lux IP strategy
Single LICENSING.md file referencing the canonical three-tier IP and
licensing strategy at github.com/luxfi/.github/blob/main/profile/README.md.

LICENSE file is unchanged; this only adds a navigational pointer.
2026-05-15 16:35:26 -07:00
Hanzo DevandGitHub 25610026e3 env: drop LUX_ prefix from env vars (noise) (#1)
Renames LUX_-prefixed env vars to canonical, non-noisy forms:

- LUX_MNEMONIC          dropped from getMnemonicEnv() lookup chain.
                         Priority is now MNEMONIC > LIGHT_MNEMONIC.
                         All callers, doc comments, and test
                         t.Setenv("LUX_MNEMONIC", "") clears removed.
- LUX_NETWORK_ID        -> NETWORK_ID
- LUX_GENESIS_DIR       -> GENESIS_DIR
- LUX_KEYS_DIR          -> KEYS_DIR  (doc fix; code already used KEYS_DIR)
- LUX_BOOTSTRAPPERS_FILE-> BOOTSTRAPPERS_FILE
- LUX_PCHAIN_ALLOCS     -> PCHAIN_ALLOCS
- LUX_PCHAIN_ALLOCS_FILE-> PCHAIN_ALLOCS_FILE

Doc string mentions of past-but-still-supported `LUX_X` env var names
("set MNEMONIC or LUX_MNEMONIC env var") are also dropped — the env
var is just MNEMONIC now.

LUX_DISABLE_CCHAIN / LUX_CCHAIN_GENESIS_FILE history left in LLM.md
(historical changelog table) and chain_shards_test.go (comment
explicitly documenting the data-driven replacement contract). These
are documenting past behavior, per task convention for historical
references.

BREAKING: external callers/operators must update env var names. Per
CLAUDE.md no-backwards-compatibility rule, the old forms are removed.
2026-05-15 16:10:32 -07:00
Hanzo AI 9561b05f84 configs: mainnet C-Chain gasLimit 12M; devnet validator restructure
- configs/mainnet/cchain.json + the embedded cChainGenesis blob in
  configs/mainnet/genesis.json: gasLimit 0x3b9aca00 (1G) → 0xb71b00 (12M)
  to match the deployed mainnet C-Chain genesis.
- configs/mainnet/genesis.json: also reorders evmTimestamp into the canonical
  alphabetical position inside the cChainGenesis JSON blob (no semantic change).
- configs/devnet/genesis.json + configs/devnet/pchain.json: drop the trailing
  three initialStakers, leaving 3 stakers wired to real luxd staker certs
  (fixed publicKey + mldsaPublicKey for the 2 surviving + reset for the 1 we
  keep). Matches what's actually validating on lux-k8s do-sfo3-lux-devnet.
2026-05-15 09:48:24 -07:00
Hanzo AI d0c1c65c45 configs: testnet+devnet initialStakers match real luxd staker certs (post-bootstrap fix) 2026-05-15 00:12:19 -07:00
Hanzo AI 79938a41b0 configs: regenerate testnet + devnet at 1000×10M (v1.9.9 shape) 2026-05-14 20:50:01 -07:00
Hanzo AI 992b7d0745 genesis: 1000 BIP44 wallet allocations × 10M LUX, drop vesting on wallet UTXOs
Repoints BuildConfigFromEnv account-allocation derivation from the
Lux-internal hardened branch (m/44'/9000'/nid'/1'/i') to the canonical
BIP44 path (m/44'/9000'/0'/0/i) — same path derive100, BIP44 web wallets,
and any wallet-against-coin-9000 tool uses. Validator NODE identities
(NodeID + BLS + ML-DSA-65) keep the internal hardened layout; spending
keys and node identities are independently complete.

  DefaultAllocationPerAccount: 500_000_000*Lux → 10_000_000*Lux  (keys.go:36)
  DefaultNumAccounts:                       100 → 1000           (keys.go:46)
  LocalParams.MinValidatorStake:        1*Lux → 1_000_000*Lux    (params.go:64)
  TestnetParams.MinValidatorStake:      1*Lux → 1_000_000*Lux    (params.go:102)
  LocalStakingConfig.MinValidatorStake: 1*Lux → 1_000_000*Lux    (params.go:166)
  TestnetStakingConfig.MinValidatorStake: 1*Lux→ 1_000_000*Lux   (params.go:148)
  MainnetParams + MainnetStakingConfig:                          unchanged

Wallet allocations no longer carry a 100-period UnlockSchedule. With
1000 wallet entries × 100 schedule rows each, the encoded P-Chain
genesis blob overflows zapdb's single-batch write limit and luxd dies
at boot with "Txn is too big to fit into one request". Wallets get
clean InitialAmount only — fans out to a free P-Chain UTXO and a free
X-Chain UTXO at the same address per the node builder. Validator
stake allocations still carry a long-locktime UnlockSchedule so the
ProtocolVM has weight to gate them.

New LoadBIP44WalletKeysFromMnemonic returns []KeyInfo at the canonical
path so BuildConfigFromEnv flows the same derivation into validator
fallback and allocation set. BuildBIP44WalletAllocations is unchanged
and remains the path the genesis CLI -bip44-wallet-keys flag uses.

configs/local/, configs/localnet/genesis.json + pchain.json
regenerated against LIGHT_MNEMONIC. testnet/devnet/mainnet JSONs
unchanged — those need regeneration against the operator's real
$LUX_MNEMONIC via the CLI, and remain pinned to their existing
addresses until the operator re-applies.

Operator runbook to regenerate per-network:

  export LUX_MNEMONIC=...  # from KMS, in-process only, never persist
  cd ~/work/lux/genesis
  go run ./cmd/genesis -network devnet -validators 5 \
      -output configs/devnet/genesis.json -format pretty
  # then split into pchain.json (allocations + initialStake*) so the
  # embedded loader's split-file preference picks them up
  go run ./cmd/genesis -network testnet -validators 5 \
      -output configs/testnet/genesis.json -format pretty

Bumps min validator stake on local + testnet to 1M LUX as planned for
the L1-bootstrap flow; mainnet stays at 2000 LUX MinValidatorStake.

Verified end-to-end on a local single-node luxd: bootstrap succeeded,
platform.getBalance for P-local1jfdgmqduuyuxjp9sq7szp08xpynav88sd7qxvv
(BIP44 m/44'/9000'/0'/0/0 of LIGHT_MNEMONIC) returns 10_000_000_000_000
nLUX = 10M LUX, fully unlocked. xvm.getBalance for the same address on
X-Chain returns identical 10M LUX. All pkg/genesis + configs tests pass
(TestTransferPChain skipped — pre-existing live-RPC flake, see aa3d175).

Pre-existing security_profile_test.go fixed for consensus@v1.23.15
rename (LuxStrictPQ → StrictPQ, ProfileLuxStrictPQ → ProfileStrictPQ);
that was blocking the whole pkg/genesis test build at v1.9.8.
2026-05-14 17:13:05 -07:00
Hanzo AI 05299c329a configs: rename chainShardSet → chainSet
The struct already only carries shards (one field per chain shard
file), so the 'Shard' qualifier in the type name was redundant. Drop
it. Identifier is unexported and used in three sites only inside
configs/configs.go, so the rename has no API impact on consumers.

Values, not places: the type captures 'the set of primary-network
chain genesis blobs' — qualifying it by where the data came from
(shard files) is a place-leak, not a value distinction.
2026-05-14 12:57:57 -07:00
Hanzo AI a4b05b40f5 genesis: shard-driven primary chain set (X opt-in like C/Q/Z)
Make every primary-network chain (X/C/D/Q/A/B/T/Z/G/K) opt-in via its
own <x>chain.json shard. Absent shard -> empty ConfigOutput field ->
builder skips the CreateChainTx -> daemon doesn't start that chain.

Lux mainnet/testnet/devnet ship full chain set as shards; localnet and
local ship the same canonical X asset shard. P-only sovereign L1 boot
shape (<tenant> etc.) is achieved by shipping pchain.json+network.json
only — no env knob, no special builder branch.

X-Chain genesis was previously hardcoded inside FromConfig as a literal
{Symbol:"LUX",Name:"Lux",Denomination:9}. Now sourced from xchain.json
shard, which keeps the same canonical asset for Lux but lets a sibling
network (lqd / <tenant>) ship its own descriptor or ship none and
boot a P-only primary network.

builder.FromConfig:
  - HRP hoisted above the X-Chain conditional (used on both branches).
  - X-Chain construction gated on config.XChainGenesis != "".
  - Returns ids.Empty for xAssetID when X is absent.
  - Single chainEntries table replaces the per-chain switch and the
    DefaultChainGenesis stub-data fallback.
  - "platform" alias renamed to "protocol" (PChainAliases / VMAliases /
    Aliases output) — value is "P-Chain", served by ProtocolVM.

configs.loadAllChainShards / readAllChainShards:
  - Single dispatch over primaryChainShardFiles in canonical order
    (X,C,D,Q,A,B,T,Z,G,K). Adding a new primary chain is one entry +
    one slot, not a new env knob and not a new builder branch.

pkg/genesis/types.go:
  - ConfigOutput.XChainGenesis (omitempty) added; threaded through
    Config <-> ConfigOutput conversions.

Tests:
  - builder/builder_test.go: TestFromConfig_ChainSetIsShardDriven pins
    the contract — full mainnet emits 10 chains + non-empty xAssetID;
    same config with all chain shards stripped emits 0 chains + empty
    xAssetID, validators + UTXOs intact.
  - configs/chain_shards_test.go: TestGetGenesis_XChainShardPresent...
    asserts every Lux network embeds the canonical {LUX,Lux,9} asset;
    AbsentShardEmptyOptIn extended to cover xChainGenesis.
2026-05-14 12:32:53 -07:00
Hanzo AI e50786716c deps: luxfi/crypto v1.19.0 2026-05-13 11:58:35 -07:00
Hanzo AI 2bc39b5546 go.mod: bump go directive to 1.26.3 (security advisory) 2026-05-12 21:29:36 -07:00
Hanzo AI 1c62f7d640 configs: materialise Q/Z shards for Lux primary networks
Q-Chain and Z-Chain are opt-in (per the builder refactor), but every
Lux-derived primary network bakes them. Make the intent explicit: ship
qchain.json + zchain.json shards alongside cchain.json so the chain set
is data-visible rather than implied by a builder default.

Downstreams (<tenant> etc.) that want P+X only simply don't ship these
shards in their config tree. No env, no flag — the shard set is the
network's identity.

The placeholder content {"version":1,"message":"Lux Chain Genesis"} is
the same string the old loadSpecialtyChainGenesis fallback substituted;
materialising it here keeps the on-wire genesis bytes stable across the
refactor.
2026-05-12 14:02:43 -07:00
Hanzo AI 73b783f957 configs/builder: one shard pattern, no env knobs per chain
Asymmetry rip: Q-Chain and Z-Chain were MANDATORY in the builder while
C/D/B/T were opt-in, with three different env knobs (LUX_DISABLE_CCHAIN,
LUX_DISABLE_QCHAIN, LUX_DISABLE_ZCHAIN) controlling embed-time inclusion.
That meant adding a new primary-network chain meant adding both a builder
case AND an env knob AND a *_GENESIS_FILE override — three places to keep
in sync, and operators got to wonder which knob applied to which chain.

Replaced with one pattern, one place:

- builder.go: all opt-in chains in a single table-driven slice, each row
  is `{genesisData, vmID, name}`. Adding a chain is one row, not a new
  conditional. P and X stay mandatory (P is implicit, X anchors fees).
- configs.go: loadOptionalChainShard / readDirShard load per-chain JSON
  shards. Shard present → chain baked into primary genesis. Shard absent
  → empty string → builder skips the entry. fs.ErrNotExist is the gate;
  every other read failure surfaces.
- LUX_DISABLE_*CHAIN env knobs deleted. LUX_*CHAIN_GENESIS_FILE deleted.
  loadSpecialtyChainGenesis / specialtyChainResult / DefaultPlaceholderGenesis
  deleted — all dead code now.

Runtime tracking is unchanged: luxd's --track-chains / --track-all-chains
gates which baked chains a particular node actually runs. Baked-but-untracked
chains stay dormant. Bake = "is this chain part of the network's identity";
track = "is this node validating it". Decomplected.

To run a P+X-only network (<tenant> etc.): ship a config tree with just
network.json + pchain.json (+ securityProfile.json). No cchain.json,
qchain.json, or zchain.json. No flag, no env, no patched binary.

Tests:
- TestGetGenesis_CChainShardPresentEmbedsCChainGenesis: embedded networks
  with cchain.json shards do emit a non-empty cChainGenesis.
- TestBuildGenesisFromDir_AbsentShardEmptyOptIn: FS fallback with only
  network.json + pchain.json produces empty cChain/qChain/zChain fields.
2026-05-12 13:44:55 -07:00
Hanzo AI 29f1c0ea59 genesis: pin StrictPQ profile against consensus@v1.23.15 hashes
Production e2e PQ wire-up:
  mainnet, testnet, local, localnet → StrictPQ (0x01)
  devnet                            → Permissive (0x02) for dev workflows

Replaces the v1.22.87-era ForkClassicalCompatUnsafe (0x80) pins which
collide with v1.23.15's tightened registry (profiles collapsed to 3
canonical bundles; 0x80 is no longer in ProfileByID's switch). The
luxfi/node v1.26.17 build resolves these pins at boot and stamps the
profile through to every wire-level gate (peer handshake, mempool,
validator scheme, EVM contract auth, C-Chain plugin config).

Verified live at boot:
  SECURITY PROFILE: STRICT
  PROFILE HASH:     cf3f2cd7b54cfc2c…
  POST-QUANTUM:     true
  NIST-FRIENDLY:    true
  CLASSICAL SNARKS: forbidden
  BLS FALLBACK:     forbidden

Hashes computed via consensus.ChainSecurityProfile.ComputeHash() — when
consensus rolls, regenerate pins via the same path (a CI guard against
silent profile drift is the next layer).
2026-05-12 13:11:50 -07:00
Hanzo AI b5941fd1b7 pkg/genesis: GetConfigFromDir also reads securityProfile.json shard
node calls genesiscfg.GetConfig(networkID) at boot which routes through
GetConfigFromDir, NOT the configs/ embedded path. Without this shard read,
the SecurityProfile pin was silently dropped between the on-disk genesis tree
and the runtime Config — the F102 wire-up only worked for paths that went
through ConfigOutput marshal/unmarshal.

Now: shard read in GetConfigFromDir mirrors the configs/configs.go embedded
loader, so the pin flows whether node loads from embedded FS, on-disk
canonical tree, or operator override directory.
2026-05-11 23:14:23 -07:00
Hanzo AI 2e8a0aea83 genesis: pin SecurityProfile per network (closes F102 wire-up)
- securityProfile.json shard per network embeds the locked ChainSecurityProfile
  pin (ProfileID + 48-byte hash). mainnet/testnet/devnet pin
  ForkClassicalCompatUnsafe (0x80) until operators migrate validator keys;
  local/localnet pin LuxStrictPQ (0x01).
- configs.go loaders (embedded + FS fallback) read the shard and stamp it into
  ConfigOutput.SecurityProfile so the pin survives the marshal/unmarshal trip
  into Config.
- pkg/genesis ParseConfigOutput now copies SecurityProfile through — without
  this the field was silently dropped, leaving Resolve() unable to verify the
  pin at boot and the node warning "genesis carries no SecurityProfile pin —
  node boots in classical-compat mode" for every network.
- security_profile_test renamed to current symbol names (LuxStrictPQ,
  ProfileLuxStrictPQ — consensus v1.23.5 renamed away from the StrictPQ alias).

Profile hashes computed from luxfi/consensus@v1.23.5 ChainSecurityProfile
ComputeHash():
  ForkClassicalCompatUnsafe (0x80): 07fff4ce64024e22…dc61d656c
  LuxStrictPQ                (0x01): 93efd103aaf4b85e…323bfcd0
2026-05-11 23:11:14 -07:00
Hanzo AI 8ab4d790b1 go.mod: promote luxfi/consensus to direct require
security_profile.go imports consensus/config directly — it's not indirect.
Goimports/go mod tidy was leaving the marker stale.
2026-05-11 22:37:30 -07:00
Hanzo AI 25950029b4 genesis: drop Lux prefix from profile identifiers
Mirror of consensus/config rename. genesis package now reads/writes
ProfileStrictPQ / ProfilePermissive / ProfileFIPS instead of the
Lux-prefixed names. Wire-string assertions updated to "STRICT_PQ" /
"PERMISSIVE" / "FIPS".
2026-05-11 16:51:10 -07:00
Hanzo AI 72b5bbcbb2 configs: LUX_DISABLE_ZCHAIN / LUX_DISABLE_QCHAIN env knobs
Mirror the LUX_DISABLE_CCHAIN three-precedence pattern for Q-Chain
(Quantum VM) and Z-Chain (ZK VM). Downstream forks that run only
P+X on the primary network (<tenant> is the first) set
LUX_DISABLE_QCHAIN=1 and LUX_DISABLE_ZCHAIN=1 to omit those entries
from the marshalled primary genesis, and builder.FromConfig's
'if config.QChainGenesis != ""' / 'ZChainGenesis' guards skip the
entries at build time.

Precedence (identical to C-Chain knob):
  1. LUX_DISABLE_<X>CHAIN=1 → empty (omit from primary genesis)
  2. LUX_<X>CHAIN_GENESIS_FILE=<path> → file contents verbatim
  3. unset → DefaultPlaceholderGenesis

Q-Chain is the post-quantum primitives chain; Z-Chain is the
zero-knowledge primitives chain. Both are Lux Network-specific and
have no place in the primary genesis of downstream L1s.
2026-05-11 14:43:04 -07:00
Hanzo AI 8edc83efc0 genesis: pin ChainSecurityProfile into Config + Resolve at load (closes F102 genesis layer)
Adds SecurityProfile to genesis.Config and ConfigOutput as an optional
pin-by-ID + pin-by-hash for the chain-wide ChainSecurityProfile. JSON
shape:

  "securityProfile": {
    "profileID": 1,
    "profileHashHex": "<96 hex chars of SHA3-384 ComputeHash>"
  }

SecurityProfile.Resolve() loads the canonical profile via
consensus/config.ProfileByID, runs Validate(), recomputes ComputeHash,
and refuses on any mismatch — a forked binary that swaps in a different
canonical profile content fails genesis boot.

This closes the genesis half of red-team F102: previously this package
never imported consensus/config.ChainSecurityProfile so no genesis
file could enforce the locked profile. The node-side wiring lands in
the follow-up commit.

Bumps luxfi/consensus v1.22.63 → v1.23.5 (the version that ships
ProfileByID, ProfileHash binding, and the closures for F96/F100/F101/
F107/F109).

Adds 6 regression tests for the load + JSON round-trip path.
2026-05-10 20:56:31 -07:00
Hanzo AI 4f23ef1402 genesis: swap mldsa65 keygen from cloudflare/circl to luxfi/crypto/pq/mldsa
luxfi/crypto v1.18.4 exposes the canonical NewKeyFromSeed entry point for
ML-DSA-65 (and mldsa44 / mldsa87) that the genesis HIP-0077 derivation
needed. Closes the TODO at the prior CIRCL stop-gap import.

The HIP-0077 SHAKE-256(label || child_seed) expansion still happens at
mldsaKeygenFromChildSeed; the resulting 32-byte xi is passed verbatim to
mldsa65.NewKeyFromSeed. At len == 32 the canonical package wires the
seed straight into FIPS 204 5.1 KeyGen, so the keypair is byte-for-byte
reproducible against the prior CIRCL call (which this package wraps
unchanged).

  go.mod: luxfi/crypto v1.17.44 -> v1.18.4
  go.mod: cloudflare/circl moves from direct -> indirect

Patch-bump.
2026-05-10 20:01:41 -07:00
Hanzo AI aa3d175818 ci: also skip TestTransferPChain (live RPC to api.lux-dev.network)
First CI run flagged a second pre-existing failure: TestTransferPChain
in pkg/genesis/do_transfer_test.go makes a real outbound JSON-RPC call
to https://api.lux-dev.network/ext/bc/P, which fails in CI because
that endpoint's TLS cert is a Traefik-issued internal cert that
doesn't match the public hostname.

This is an integration test, not a unit test — it does not belong in
push/tag CI. Skipped via -skip regex in both ci.yml and release.yml
until it is either gated on testing.Short() / build tags or moved into
a dedicated integration job that has access to a real devnet.
2026-05-10 17:43:59 -07:00
Hanzo AI 7cc8aa135f ci: add CI + Release workflows; gofmt -s; bump metric v1.4.11→v1.4.12
The repo had no .github/workflows/ at all, so tag pushes never produced
release binaries.

Added:

  .github/workflows/ci.yml       go vet, gofmt -s -d, go test (push/PR to main)
  .github/workflows/release.yml  build genesis for linux|darwin|windows
                                 amd64/arm64 + sha256, attach to GH release

CI runs `go test -skip TestGetGenesisLocalnet ./...` until the embedded
localnet genesis file is regenerated (expected 5e17 wei / 0.5 LUX, got
5e14). All other tests are green.

While here:
  * go.mod bumped Go directive 1.26.1 → 1.26.2 (matches new toolchain
    used by ci.yml / release.yml).
  * luxfi/metric v1.4.11 → v1.4.12. v1.4.11 had a build-tag bug where
    both process_metrics_other.go and process_metrics_windows.go used
    //go:build windows, causing duplicate symbols on GOOS=windows. v1.4.12
    fixes the tag to //go:build !unix && !windows. Required for the
    windows/amd64 leg of the release matrix.
  * `gofmt -s -w` on four pre-existing unformatted files so the new
    gofmt CI gate is green from day one.
2026-05-10 17:41:27 -07:00
Hanzo AI 487513c844 genesis: HD branch hardening + LIGHT_MNEMONIC production refusal (F12/F30/F31/F35)
HIP-0077 §"Identity" HD path alignment. Every public derivation entry
point now takes the network id and derives on the per-network
hardened branches mandated by the spec:

  m/44'/9000'/nid'/0'/i'   -> device_pq_key[i]   (ML-DSA-65)
  m/44'/9000'/nid'/1'/i'   -> device_lux_key[i]  (secp256k1)

All five levels (44', 9000', nid', branch, index) are now hardened on
the secp256k1 branch. Per-network hardening (nid' at the account level)
means the same mnemonic on different network ids derives fully
independent keypairs — no cross-network key reuse possible.

ML-DSA-65 keypair: expand the 32-byte BIP-32 child seed via
SHAKE-256("LUX/HIP-0077/mldsa65" || child_seed) into the 32-byte xi
that FIPS 204 §5.1 KeyGen consumes. Domain-separated so future schemes
sharing the same BIP-32 child seed cannot collide. ML-DSA backend:
cloudflare/circl mldsa65.

LoadKeysFromMnemonicEnvForNetwork blacklists 6 known public mnemonics
(BIP-39 abandon vector, Hardhat default, Trezor demo) and refuses to
proceed in production — closes F31. The CI/dev path still works with
LUX_LIGHT_MNEMONIC=1 explicitly set.

cmd/checkkeys + cmd/genesis: thread the network id through every
call site. cmd/derive100: helper that derives the first 100 PQ+secp
keypairs per network id for the genesis allocation.

Tests: pkg/genesis PASS (HD branches + LIGHT_MNEMONIC guard).
CHANGELOG.md added.

Breaking signature change for LoadKeysFromMnemonic /
LoadKeysFromMnemonicEnv / BuildWalletAllocations / BuildWalletKeyHex
(every entry point now takes nid uint32). Patch-bump.
2026-05-10 17:19:38 -07:00
Hanzo AI a3f4adc49f configs: LUX_DISABLE_CCHAIN=1 omits C-Chain from primary genesis
Adds an opt-out for the embedded C-Chain that gets baked into every
primary network genesis. With LUX_DISABLE_CCHAIN=1 the cchainData byte
slice stays empty, ConfigOutput.CChainGenesis is "", and downstream
builder.FromConfig's `if config.CChainGenesis != ""` guard skips the
C-Chain entry entirely — no more chainId 31337 silently mounted at
/ext/bc/C/rpc on networks that don't want it.

Why this knob exists: forks that run their own EVM blockchain via
CreateChainTx (<tenant>, etc.) don't use the C-Chain at all — but the
embedded localnet/cchain.json was being read unconditionally, so an
SRE running `lqd --network-id=1337` with no C-Chain env vars set
still got one. That's a footgun for any service that hard-codes the C
alias and a confusion source when you grep for /bc/C and it's there.

Three tests pin the behavior:

  • TestGetGenesis_DisableCChain — set knob → cChainGenesis is empty
    in the marshalled primary genesis, networkID still 1337.

  • TestGetGenesis_DisableCChainOverridesFile — disable wins over the
    LUX_CCHAIN_GENESIS_FILE override; an SRE setting both during a
    misconfig probe gets the no-C-Chain outcome rather than silently
    landing the file's content on the primary.

  • TestGetGenesis_DisableCChainDefault — unset knob still embeds
    C-Chain (regression guard so a careless precedence edit doesn't
    silently strip C-Chain from mainnet/testnet/devnet).

go.mod / go.sum churn is `go mod tidy` resolving previously-stale
entries — unrelated to the C-Chain change but required for the test
suite to build.
2026-05-09 16:27:36 -07:00
Hanzo AI 9838b15fb8 feat(configs): operator-overridable C-Chain genesis via LUX_CCHAIN_GENESIS_FILE
Adds env-driven override at the lowest layer of the genesis loader so
downstream networks (e.g. <tenant> at chainId 8675312) can reuse a
stock lqd binary without forking configs/{network}/cchain.json.

Resolution order in loadEmbeddedGenesisWithDynamic:
  1. LUX_CCHAIN_GENESIS_FILE — absolute path to a JSON file. Read at
     boot, used verbatim as the C-Chain genesis bytes.
  2. embedded configs/{network}/cchain.json (the previous default,
     immutable per-network).

Net behaviour for stock callers is unchanged: env unset → embedded
default. The override only fires when the file exists and is readable;
a misconfigured path fails loud with a wrapped error so misconfiguration
isn't silently masked by the embedded fallback.

Pairs with luxfi/node@d0a248f5 (LUX_AUTOMINE_CCHAIN_GENESIS_PATH for the
single-node automine path). The two cover both startup modes lqd uses.
2026-05-08 17:18:27 -07:00
Hanzo AI 88b6561b5b configs: bump every chain's gasLimit to 1,000,000,000 (1B)
Across all envs (mainnet/testnet/devnet/local/localnet) and all chains
(C-Chain primary + Zoo + SPC + Hanzo + Pars L2s), the EVM block gas
limit and the matching root genesis.gasLimit field are bumped to 1B.
targetGas tracks at half the limit (500M) to keep the existing 50%
fee-curve target ratio.

Why: 100M was the old practical ceiling and was already getting hit on
bulk security-token deploys (12k+ tokens) where each constructor burns
~1.3M gas. At 100M that's ~75 deploys/block; at 1B it's ~750.

Production note: bumping mainnet's gasLimit changes the cChain genesis
hash. Existing state imports against the old 12M-gasLimit cChain are
invalidated. The canonical hash table in the package CLAUDE.md needs to
be regenerated after this lands.
2026-05-07 22:04:37 -07:00
Hanzo AI ac4c21fb82 configs(localnet): drop cChainGenesis — Lux primary network has no C-Chain
The Lux primary network on local-1337 hosts P + X + the static native chains
(A/B/D/G/K/Q/T/Z); the EVM is provisioned per-tenant as a subnet chain
(<tenant> EVM / <tenant> DEX / Liquid FHE), not baked into primary as C-Chain.
Carrying cChainGenesis here just bound the network to a 31337 stub that
nobody actually uses and confused tools that derive deployer keys from
LIGHT_MNEMONIC into thinking the local C-Chain alloc was the dev funding.

The lux/node genesis builder treats CChainGenesis as opt-in
(`if config.CChainGenesis != ""` at builder.go:618) so dropping the field
is safe — primary still starts with all required chains, just without C.
2026-05-07 21:04:07 -07:00
Hanzo AI 73d40da382 configs(localnet): regen from LIGHT_MNEMONIC for deterministic local dev
The localnet/genesis.json was generated from a different mnemonic — wallet[0]
was X-local1xhtylulkrrm... which doesn't match what BuildWalletKeyHex(0) returns
when LIGHT_MNEMONIC is set in env. This broke local-dev tools (cmd/create-evm,
cmd/deploy-dex, cmd/deploy-fhe in <tenant>/node) that derive keys from
LIGHT_MNEMONIC and try to use them against the local P-Chain — getUTXOs returned
empty, CreateChainTx failed with "couldn't get tx: not found".

Regen via:

  MNEMONIC=$LIGHT_MNEMONIC go run ./cmd/genesis \
    -network local \
    -validators 5 \
    -wallet-keys 100 \
    -wallet-amount 500000000 \
    -format json -output -

merged into configs/localnet/genesis.json + pchain.json (preserving the
a/b/c/d/g/k/q/t/z chain genesis fields). Now wallet[0]=P-local1jfdgmqduuyux...
matches LIGHT_MNEMONIC m/44'/9000'/0'/0/0 exactly, and indices 0..99 each carry:

  - 500M LUX vested over 100 years (P-Chain main alloc)
  - 500M LUX FREE/spendable (extra wallet alloc, indices 100..199)

so a fresh local 1337 cluster bootstraps with 100 fully-funded P/X wallets that
both the lux/node + <tenant>/node toolchains can derive against without any
special config.

unlockSchedule normalised to [] (was null in jq output) for strict consumers.
2026-05-07 20:16:50 -07:00
Hanzo AI 19165b3c38 configs: add IsCustom() — mirror luxfi/constants v1.5.2
Same classification at the genesis layer as at the constants layer.
A user spinning up a private primary network on any non-well-known
ID gets:

  IsCustom(id)             == true at both layers
  networkNameFromID(id)    == ""   (no embedded canonical genesis)
  constants.GetHRP(id)     == "custom" (P-custom1... addresses)

Callers must supply --genesis-file for any IsCustom() network since
there's no embedded config to load.
2026-05-06 18:07:15 -07:00
Hanzo AI 3eb86c7211 configs: split LocalID and CustomID into distinct constants
Mirrors the same split that just landed in luxfi/constants v1.5.1:

  LocalID         = 1337      (canonical local dev primary network)
  LocalChainID    = 31337     (canonical local C-Chain EVM ID)
  CustomID        = 0         (sentinel for user-defined networks)
  CustomChainID   = 0         (sentinel for user-defined C-Chain IDs)
  LocalnetID      = LocalID   (deprecated alias)
  LocalnetChainID = LocalChainID (deprecated alias)

Before, CustomID was an alias of LocalnetID (both 1337) which made
"this is the local dev network" indistinguishable from "this is some
other custom network the user supplied". After the split, callers can
say LocalID (1337) for the well-known local dev net, CustomID (0) for
genuinely custom networks. Addresses on a custom network use the
"custom" HRP from luxfi/constants — i.e. P-custom1..., X-custom1...
— so they're visually distinct from the local-net "X-local1..." form.

networkNameFromID switch updated to recognise LocalID + LocalChainID.
2026-05-06 16:10:11 -07:00
Hanzo AI 1e8878cb87 canonical: SubnetID → ChainID; drop redundant L1ID/SubnetID fields
Lux unifies subnet (validator-set) and chain identity into a single
ChainID — every chain identifies itself by its ChainID, no separate
'subnet identifier' needed.

Wire/interface changes (BREAKING — coordinated rollout):
  - node/message/wire: ChainSubnetPair struct → ChainPingEntry; collapsed
    duplicate SubnetId field; ChainSubnetPairs → ChainIds
  - node/proto/zap/p2p: SubnetID → ChainID
  - node/vms/chainadapter: ICPBlock/ICPSubnet SubnetID → ChainID
  - validators/uptime: Calculator interface params subnetID → chainID
  - consensus/core/router: Connected method param subnetID → chainID
  - p2p/message: outbound_msg_builder var renames

Config/UI changes (cleanup):
  - genesis/pkg/genesis ChainEntry: dropped redundant SubnetID field
  - tui/views/ChainStatus: dropped redundant SubnetID field
  - cli: comment cleanup
  - benchmarks: deploy-subnets var rename
2026-05-05 22:11:01 -07:00
Hanzo AI e2d0e784c0 canonical: rename SubnetID → L1ID in user-facing structs (Lux uses L1/L2 terminology)
Renamed in display/config layer:
  - genesis/pkg/genesis/types.go ChainEntry: SubnetID → L1ID, json:'subnetID' → json:'l1ID'
  - tui/views/{types.go,chains.go} ChainStatus: SubnetID → L1ID, label 'Subnet ID' → 'L1 ID'
  - cli/pkg/chain/deployStatus.go comment

NOT touched (would require coordinated network upgrade — separate work):
  - validators/uptime Calculator interface (cross-package API)
  - consensus/core/router/router.go Connected method
  - node/message/wire types.go SubnetId field (WIRE PROTOCOL byte)
  - node/proto/zap/p2p (protobuf-generated wire)
  - p2p/proto/p2p/p2p.pb.go (protobuf-generated wire)
  - netrunner-sdk/rpcpb/rpc.proto (gRPC API field)
2026-05-05 21:53:45 -07:00
Hanzo AI 20150c0ec2 canonical: drop SubnetEVMTimestamp alias — single evmTimestamp field
Lux EVM activation is gated by a single canonical chain config field:
  EVMTimestamp / json:'evmTimestamp'

The legacy 'SubnetEVMTimestamp' / 'subnetEVMTimestamp' field was a duplicate
alias for the same activation. Removed:
  - geth/params/config.go: ChainConfig.SubnetEVMTimestamp field
  - geth/params/config.go: IsEVM() now checks EVMTimestamp only
  - geth/params/config.go: isLuxL2Chain check uses EVMTimestamp only
  - all genesis JSON configs (mainnet/testnet/devnet × all chains)
  - all helm chart genesis files (charts/lux/genesis/*.json)
  - all docs (LLM.md, *.mdx)

One field, one way.
2026-05-05 21:30:08 -07:00
Hanzo AI 1c0d11b5de docs: scrub upstream 'Subnet-EVM' brand; rename 'Subnet Chains' → 'L2 Chains' 2026-05-05 20:26:10 -07:00
Hanzo AI 7d6f991fbd docs: canonical EVM VM ID = mgj786NP… (single, no aliases) 2026-05-05 20:16:26 -07:00
Hanzo AI 86a0400071 fix(pars): 0x9011 genesis alloc 112.6B → 7B per Pars EVM spec
Was 0x16bcc41e90000000000000000 (112.589 billion) — copy-paste error from
Lux/Zoo C-Chain 2T value. Pars EVM is its own L1 with distinct tokenomics;
deployer gets 7B PARS across all three envs (mainnet 494949, testnet 7071,
devnet 7072).

New alloc hex: 0x169e43a85eb381aa58000000 (= 7,000,000,000 × 10^18 wei).

Running chain on api.lux.network/ext/bc/pars/rpc still has stale alloc
(~1000 PARS to 0x9011); needs P-chain CreateChainTx with these bytes to
relaunch with correct genesis.
2026-04-24 19:06:28 -07:00
Hanzo AI 075dd23c0e deps: update go.mod/go.sum 2026-04-19 17:04:31 -07:00
Hanzo AI 11a111d1b0 fix: restore CustomID alias for backward compatibility with netrunner
netrunner v1.15.11 references configs.CustomID which was renamed to
LocalnetID. Add CustomID = LocalnetID alias. Also regenerate go.sum
after stale checksum entries from repo rewrite.
2026-04-12 12:42:08 -07:00
Hanzo AI 536f01bbb8 fix: ParseConfigOutput for embedded genesis — string→binary address conversion 2026-04-09 19:59:53 -07:00
Hanzo AI e12a9b101f fix: remove Liquid L2 chain IDs — belongs in <tenant>/node not luxfi/genesis 2026-04-08 21:23:04 -07:00
Hanzo AI a6cf649e3c fix: genesis types 2026-04-08 20:38:57 -07:00
Hanzo AI 8c9c0cb744 fix: lock in Liquid L2 chain IDs — EVM/DEX/FHE × mainnet/testnet/devnet/localnet 2026-04-08 20:31:46 -07:00
Hanzo AI a827dc3678 feat: localnet 100 accounts × 500M LUX from LIGHT mnemonic
- 100 BIP-44 addresses (m/44'/60'/0'/0/{0..99})
- 200 P/X allocations (100 accounts × X+P), 5 stakers
- 101 C-chain alloc entries (100 accounts + warp precompile)
- networkID=1337, EVM chainId=31337
- gen-localnet.py script for regeneration
- TestGetGenesisLocalnet verifies all allocations
2026-04-08 13:55:01 -07:00
Hanzo AI 0ef529faab fix: rename custom → localnet, EVM chainId 31337
- custom/ → localnet/ (1337 is localnet, not custom)
- LocalnetID=1337 (P-Chain networkID)
- LocalnetChainID=31337 (C-Chain EVM chainId, matches Anvil convention)
- CustomID deprecated → use LocalID from luxfi/constants
- anything not mainnet/testnet/devnet/localnet is custom (--genesis-file)
2026-04-08 13:33:11 -07:00
Hanzo AI 7a09480b25 feat: derive BLS signer keys from mnemonic for deterministic genesis (devnet real staking) 2026-04-08 08:50:19 -07:00
Hanzo AI d1a76eb743 feat: add PQIdentity to genesis staker — ML-DSA + Corona key binding 2026-04-07 00:54:22 -07:00
Hanzo AI a5e669faf4 feat: add BuildWalletKeyHex — export BIP44 key at index for bootstrap tools 2026-04-06 23:18:30 -07:00
Hanzo AI 53eece69b8 security: zero EWOQ key balance in custom config — use mnemonic keys only 2026-04-06 22:58:43 -07:00