mirror of
https://github.com/luxfi/tokenomics.git
synced 2026-10-30 15:15:35 +00:00
Ethereum Media 0x31e0F919 probed live at block 25612642: 50 tokens, ids 1-50 contiguous, zero duplicates, zero malformed/truncated tokenURIs, all 50 with a non-zero content hash. The "~43 interrupted URIs" in the task backlog does not exist on chain and has no counterpart in the broadcast record. The only Ethereum defect is that every tokenURI names lux.town, which has no nameservers — and that CANNOT be fixed on chain: the deployed Zora-fork Media has no setTokenURI, no setBaseURI and no owner override, only updateTokenURI gated onlyApprovedOrOwner. The repair is the client-side host rewrite to cdn.lux.network, already live. The 9 treasury duplicates were real, and the deployed fix was only half applied. MintTreasuryValidators minted a naive consecutive #33-#100 instead of the 68-serial complement; nine collided; FixTreasuryNumbers renumbered them but templated all nine payloads off validator #33 and edited only `name`. Every one kept "Validator No": 33 and artwork reading "VALIDATOR #33 / 100", so the pre-reset collection had TEN tokens rendering as #33. The existing verification passed because it only ever read `name`. Root cause: the serial was materialised 127 times as ~450 KB of literal base64 across three Solidity scripts. Fix: one registry holds each serial once, one renderer derives the metadata. data/eth-genesis-inventory.json live Ethereum state (generated, re-checkable) data/genesis-registry.json canonical 118 rows: 100 validators #1-#100 + 18 coins scripts/probe-eth-genesis.mjs eth_call/eth_getCode only, cannot send a tx scripts/gen-genesis-registry.mjs invariants: serials {1..100}, treasury set = {1..100} \ ETH validators, bonds match the ETH tokenURI, totalLuxLocked 102,583,000,000 scripts/genesis-metadata.mjs the ONE renderer (one SVG template) scripts/verify-genesis-metadata.mjs 118/118 deployed payloads reproduced byte-for-byte; the 9 defective ones pinned as a regression test scripts/gen-remint-plan.mjs un-executed re-mint plan; mints each treasury validator AT its canonical serial, so no renumber step exists to get half-applied No transaction is sent by anything in this repo. C-Chain 0x004287C4 confirmed wiped (eth_getCode = 0x) — the re-mint remains owner-gated and deferred.
295 lines
15 KiB
Python
295 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
"""Verify the ETH -> C-Chain Genesis NFT migration and emit data/migration-map.json.
|
||
|
||
Ground truth inputs (never invent numbers here):
|
||
- ethereum/HOLDER-SNAPSHOT.md : the 50 ETH tokens (tokenId, class, bond, holder)
|
||
- data/cchain-current.json : live C-Chain GenesisNFTs state (118 tokens)
|
||
- data/eth-authorized-supply.json: authorized vs minted per Drop token type
|
||
|
||
Run: python3 data/gen_migration_map.py
|
||
Exits non-zero if any cross-check fails.
|
||
"""
|
||
import json, re, sys, datetime, pathlib
|
||
|
||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||
SNAP = ROOT / "ethereum" / "HOLDER-SNAPSHOT.md"
|
||
CCHAIN = ROOT / "data" / "cchain-current.json"
|
||
AUTH = ROOT / "data" / "eth-authorized-supply.json"
|
||
OUT = ROOT / "data" / "migration-map.json"
|
||
|
||
TREASURY = "0x9011e888251ab053b7bd1cdb598db4f9ded94714"
|
||
|
||
# C-Chain contract tier -> bond (from GenesisNFTs.sol _getLuxForTier)
|
||
TIER_BOND = {"GENESIS": 1_000_000_000, "VALIDATOR": 100_000_000, "MINI": 10_000_000, "NANO": 1_000_000}
|
||
# ETH bond -> (contract tier used for a COIN of that denomination on C-Chain)
|
||
BOND_TIER = {1_000_000_000: "GENESIS", 100_000_000: "VALIDATOR", 10_000_000: "MINI", 1_000_000: "NANO"}
|
||
# C-Chain coin name prefix -> bond
|
||
COIN_NAME_BOND = {"GENESIS COIN": 1_000_000_000, "VALIDATOR COIN": 100_000_000,
|
||
"MINI COIN": 10_000_000, "NANO COIN": 1_000_000}
|
||
|
||
errors = []
|
||
def check(cond, msg):
|
||
if not cond:
|
||
errors.append(msg)
|
||
|
||
# ---- 1. parse ETH holder snapshot table --------------------------------------
|
||
eth = {} # tokenId -> {class, bond, holder}
|
||
row = re.compile(r"^\|\s*(\d+)\s*\|\s*(Genesis Validator|Coin)\s*\|\s*([\d,]+)\s*\|\s*`(0x[0-9a-fA-F]+)`\s*\|")
|
||
for line in SNAP.read_text().splitlines():
|
||
m = row.match(line)
|
||
if not m:
|
||
continue
|
||
tid = int(m.group(1))
|
||
cls = "validator" if m.group(2) == "Genesis Validator" else "coin"
|
||
bond = int(m.group(3).replace(",", ""))
|
||
eth[tid] = {"class": cls, "bond": bond, "holder": m.group(4)}
|
||
|
||
check(len(eth) == 50, f"expected 50 ETH rows, parsed {len(eth)}")
|
||
check(set(eth) == set(range(1, 51)), "ETH tokenIds are not exactly 1..50")
|
||
|
||
eth_val = sum(1 for t in eth.values() if t["class"] == "validator")
|
||
eth_coin = sum(1 for t in eth.values() if t["class"] == "coin")
|
||
check(eth_val == 32, f"ETH validators {eth_val} != 32")
|
||
check(eth_coin == 18, f"ETH coins {eth_coin} != 18")
|
||
|
||
# coin denomination histogram
|
||
from collections import Counter
|
||
eth_coin_hist = Counter(t["bond"] for t in eth.values() if t["class"] == "coin")
|
||
check(eth_coin_hist[1_000_000_000] == 2, "ETH 1B coins != 2")
|
||
check(eth_coin_hist[100_000_000] == 5, "ETH 100M coins != 5")
|
||
check(eth_coin_hist[10_000_000] == 8, "ETH 10M coins != 8")
|
||
check(eth_coin_hist[1_000_000] == 3, "ETH 1M coins != 3")
|
||
|
||
eth_reserve = sum(t["bond"] for t in eth.values())
|
||
check(eth_reserve == 34_583_000_000, f"ETH redemption reserve {eth_reserve} != 34,583,000,000")
|
||
|
||
eth_holders = {t["holder"].lower() for t in eth.values()}
|
||
check(len(eth_holders) == 28, f"distinct ETH holders {len(eth_holders)} != 28")
|
||
|
||
# ---- 2. load C-Chain live state ----------------------------------------------
|
||
cc = json.loads(CCHAIN.read_text())
|
||
toks = {t["id"]: t for t in cc["tokens"]}
|
||
check(cc["totalMinted"] == 118, "C-Chain totalMinted != 118")
|
||
check(len(toks) == 118, "C-Chain token array != 118")
|
||
|
||
def classify(tok):
|
||
"""Correct class by FULL name, not substring (fixes the buggy summary)."""
|
||
name = (tok["name"] or "")
|
||
u = name.upper()
|
||
if u.endswith("COIN") or " COIN #" in u or u.endswith("COIN"):
|
||
return "coin"
|
||
if "GENESIS VALIDATOR" in u or "GENESIS COIN" not in u and "VALIDATOR" in u and "COIN" not in u:
|
||
return "validator"
|
||
if u.startswith("LUX GENESIS VALIDATOR"):
|
||
return "validator"
|
||
return None
|
||
|
||
# ---- 3. people migration (cid 0..49 <-> eth 1..50) ---------------------------
|
||
people = []
|
||
for cid in range(0, 50):
|
||
tok = toks[cid]
|
||
etid = cid + 1
|
||
e = eth[etid]
|
||
owner = tok["owner"].lower()
|
||
# holder equality: C-Chain owner == ETH holder of eth token (cid+1)
|
||
check(owner == e["holder"].lower(),
|
||
f"holder mismatch cid {cid}: cchain {owner} != eth#{etid} {e['holder'].lower()}")
|
||
check(tok["holder"] == "person", f"cid {cid} not person-held")
|
||
name = tok["name"] or ""
|
||
if e["class"] == "validator":
|
||
cls, tier, bond = "validator", "GENESIS", 1_000_000_000
|
||
if name: # #49 (cid 49) has null name in the export
|
||
check("GENESIS VALIDATOR" in name.upper(),
|
||
f"cid {cid} expected validator name, got {name!r}")
|
||
else:
|
||
cls, tier, bond = "coin", BOND_TIER[e["bond"]], e["bond"]
|
||
# name tier must agree with ETH bond
|
||
prefix = name.rsplit(" #", 1)[0].upper() if name else ""
|
||
check(prefix in COIN_NAME_BOND and COIN_NAME_BOND[prefix] == e["bond"],
|
||
f"coin tier/bond mismatch cid {cid}: name {name!r} vs eth bond {e['bond']:,}")
|
||
people.append({
|
||
"eth_token_id": etid, "cchain_token_id": cid, "class": cls, "tier": tier,
|
||
"bond_lux": bond, "cchain_name": tok["name"], "holder": tok["owner"],
|
||
"owner_verified": True,
|
||
})
|
||
|
||
# ---- 4. treasury completion (cid 50..117) ------------------------------------
|
||
treasury = []
|
||
tre_serials = []
|
||
ser_re = re.compile(r"Lux Genesis Validator #(\d+)")
|
||
for cid in range(50, 118):
|
||
tok = toks[cid]
|
||
check(tok["owner"].lower() == TREASURY, f"cid {cid} treasury not 0x9011")
|
||
check(tok["holder"] == "treasury", f"cid {cid} not treasury-held")
|
||
m = ser_re.search(tok["name"] or "")
|
||
check(bool(m), f"cid {cid} not a 'Lux Genesis Validator #N' name: {tok['name']!r}")
|
||
serial = int(m.group(1))
|
||
tre_serials.append(serial)
|
||
treasury.append({
|
||
"cchain_token_id": cid, "class": "validator", "tier": "GENESIS",
|
||
"bond_lux": 1_000_000_000, "validator_serial": serial,
|
||
"cchain_name": tok["name"], "owner": tok["owner"],
|
||
})
|
||
|
||
check(len(treasury) == 68, f"treasury count {len(treasury)} != 68")
|
||
|
||
# ---- 5. corrected summary ----------------------------------------------------
|
||
val_total = sum(1 for p in people if p["class"] == "validator") + len(treasury)
|
||
coin_total = sum(1 for p in people if p["class"] == "coin")
|
||
check(val_total == 100, f"total validators {val_total} != 100")
|
||
check(coin_total == 18, f"total coins {coin_total} != 18")
|
||
|
||
# ---- 6. validator serial coverage: people serials (1..50 subset) + treasury == 1..100
|
||
people_val_serials = []
|
||
for p in people:
|
||
if p["class"] == "validator":
|
||
# people validator display serial == its eth_token_id (global id on the original set)
|
||
people_val_serials.append(p["eth_token_id"])
|
||
all_serials = sorted(people_val_serials + tre_serials)
|
||
check(all_serials == list(range(1, 101)),
|
||
f"validator serials not exactly 1..100 (dupes/gaps): "
|
||
f"dupes={[x for x in set(all_serials) if all_serials.count(x) > 1]}")
|
||
|
||
# people take the validator serials that were validators on ETH; treasury fills the rest
|
||
tre_low = sorted(s for s in tre_serials if s <= 50)
|
||
eth_coin_ids = sorted(t for t in eth if eth[t]["class"] == "coin")
|
||
check(tre_low == eth_coin_ids,
|
||
"treasury serials <=50 do not equal the ETH coin tokenIds")
|
||
|
||
# ---- 7. C-Chain recorded bonds ----------------------------------------------
|
||
cc_val_bond = val_total * 1_000_000_000
|
||
cc_coin_bond = sum(p["bond_lux"] for p in people if p["class"] == "coin")
|
||
cc_total_locked = cc_val_bond + cc_coin_bond
|
||
check(cc_coin_bond == 2_583_000_000, f"C-Chain coin bond {cc_coin_bond} != 2,583,000,000")
|
||
|
||
# ---- 8. authorized supply sanity --------------------------------------------
|
||
auth = json.loads(AUTH.read_text())
|
||
check(auth["totals"]["authorized"] == 12211, "authorized total != 12211")
|
||
check(auth["totals"]["minted"] == 50, "authorized-file minted != 50")
|
||
|
||
# ---- emit --------------------------------------------------------------------
|
||
out = {
|
||
"generated_utc": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||
"note": "Regenerate with: python3 data/gen_migration_map.py",
|
||
"ethereum": {
|
||
"media_contract": "0x31e0F919C67ceDd2Bc3E294340Dc900735810311",
|
||
"drop_contract": "0x941E3B4fC883B1746E52814EB8574b85E6Fa4E66",
|
||
"app_contract": "0x44A210571E135C6a536564e9B14dc4DA63f3D398",
|
||
"chain": "ethereum-mainnet", "chain_id": 1, "snapshot_block": 25443474,
|
||
"minted": 50, "distinct_holders": len(eth_holders),
|
||
"redemption_reserve_lux": eth_reserve,
|
||
},
|
||
"cchain": {
|
||
"contract": "0x004287C47efc912FEc391979154454a8017A76C6",
|
||
"chain": "lux-cchain", "chain_id": 96369,
|
||
"total_minted": 118, "people_held": 50, "treasury_held": 68,
|
||
"treasury_wallet": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||
},
|
||
"summary": {
|
||
"validators_total": val_total, "coins_total": coin_total,
|
||
"coins_people_held": coin_total, "validators_people_held": val_total - len(treasury),
|
||
"validators_treasury_completion": len(treasury),
|
||
"cchain_recorded_lux_locked": cc_total_locked,
|
||
"cchain_validator_bond_lux": cc_val_bond,
|
||
"cchain_coin_bond_lux": cc_coin_bond,
|
||
},
|
||
"people_migration": people,
|
||
"treasury_completion": treasury,
|
||
"verification": {
|
||
"all_checks_passed": len(errors) == 0,
|
||
"eth_owner_equals_cchain_owner_1to1": True,
|
||
"coin_tier_matches_eth_bond": True,
|
||
"validator_serials_exactly_1_to_100": all_serials == list(range(1, 101)),
|
||
"treasury_low_serials_equal_eth_coin_ids": tre_low == eth_coin_ids,
|
||
},
|
||
}
|
||
|
||
if errors:
|
||
print("VERIFICATION FAILED:")
|
||
for e in errors:
|
||
print(" -", e)
|
||
sys.exit(1)
|
||
|
||
OUT.write_text(json.dumps(out, indent=2) + "\n")
|
||
|
||
# ---- render human-readable migration/MAPPING.md from the SAME verified data --
|
||
MAP_MD = ROOT / "migration" / "MAPPING.md"
|
||
def lux(n): # comma-grouped integer
|
||
return f"{n:,}"
|
||
|
||
lines = []
|
||
lines.append("# Genesis NFT Migration Map — Ethereum → Lux C-Chain (token by token)")
|
||
lines.append("")
|
||
lines.append("> **Generated file.** Do not hand-edit. Regenerate with")
|
||
lines.append("> `python3 data/gen_migration_map.py` (source of truth:")
|
||
lines.append("> `ethereum/HOLDER-SNAPSHOT.md` + `data/cchain-current.json`). Machine-")
|
||
lines.append("> readable form: [`data/migration-map.json`](../data/migration-map.json).")
|
||
lines.append("")
|
||
lines.append(f"- Ethereum Media (LUXNFT): `{out['ethereum']['media_contract']}` — snapshot block {out['ethereum']['snapshot_block']}")
|
||
lines.append(f"- Lux C-Chain GenesisNFTs (chain {out['cchain']['chain_id']}): `{out['cchain']['contract']}`")
|
||
lines.append(f"- Treasury / DAO wallet: `{out['cchain']['treasury_wallet']}`")
|
||
lines.append("")
|
||
lines.append("Every check below is enforced by the generator (build fails otherwise):")
|
||
lines.append("")
|
||
lines.append("- ETH token *N* re-mints 1:1 to **C-Chain token _N−1_** (C-Chain ids start at 0),")
|
||
lines.append(" `originTokenId` preserved, minted to the **same holder** — owner equality verified for all 50.")
|
||
lines.append("- A **Coin**'s C-Chain tier name matches its Ethereum bond exactly")
|
||
lines.append(" (GENESIS COIN=1B, VALIDATOR COIN=100M, MINI COIN=10M, NANO COIN=1M).")
|
||
lines.append("- The 100 Genesis-Validator **serial numbers are exactly #1–#100** — no gaps, no duplicates.")
|
||
lines.append(" ⚠️ **This check reads the `name` field only.** Nine treasury tokens passed it while")
|
||
lines.append(" carrying a stale `Validator No` trait and stale on-chain artwork — see")
|
||
lines.append(" [HISTORY.md](HISTORY.md) (2026-07-09, item 2). Agreement between name, trait and art")
|
||
lines.append(" is enforced separately by [`scripts/verify-genesis-metadata.mjs`](../scripts/verify-genesis-metadata.mjs).")
|
||
lines.append("")
|
||
lines.append("## Part 1 — Original holders (ETH #1–50 → C-Chain #0–49, 1:1)")
|
||
lines.append("")
|
||
lines.append("| ETH # | C-Chain # | Class | Tier | Bond (LUX) | C-Chain name | Holder (owner verified ✓) |")
|
||
lines.append("|---:|---:|---|---|---:|---|---|")
|
||
for p in people:
|
||
nm = p["cchain_name"] if p["cchain_name"] else "_(art/metadata restored)_"
|
||
lines.append(f"| {p['eth_token_id']} | {p['cchain_token_id']} | {p['class']} | {p['tier']} | "
|
||
f"{lux(p['bond_lux'])} | {nm} | `{p['holder']}` |")
|
||
lines.append("")
|
||
lines.append(f"Original-holder subtotal: **{val_total-len(treasury)} Genesis Validators + "
|
||
f"{coin_total} Coins = 50 tokens**, across {len(eth_holders)} distinct wallets. "
|
||
f"Bonded reserve carried over 1:1 = **{lux(eth_reserve)} LUX**.")
|
||
lines.append("")
|
||
lines.append("## Part 2 — Treasury completion (C-Chain #50–117 → authorized Validators #1–100)")
|
||
lines.append("")
|
||
lines.append("The Ethereum drop authorized **100** Genesis Validators but only 32 were sold. On")
|
||
lines.append("2026-07-09 the **68** unminted validators were minted to the DAO treasury, filling")
|
||
lines.append("every remaining serial so the collection holds exactly #1–#100. The serials the")
|
||
lines.append("treasury filled **≤ #50 are precisely the Ethereum tokenIds that had been spent on")
|
||
lines.append("Coins** (verified) — the rest complete #51–#100.")
|
||
lines.append("")
|
||
lines.append("| C-Chain # | Validator serial | Class | Bond (LUX) | Owner (treasury) |")
|
||
lines.append("|---:|---:|---|---:|---|")
|
||
for t in treasury:
|
||
lines.append(f"| {t['cchain_token_id']} | #{t['validator_serial']} | validator | "
|
||
f"{lux(t['bond_lux'])} | `{t['owner']}` |")
|
||
lines.append("")
|
||
lines.append(f"Treasury subtotal: **{len(treasury)} Genesis Validators** (serials "
|
||
f"{', '.join('#'+str(s) for s in sorted(tre_serials)[:6])} … #100), all held by the DAO treasury.")
|
||
lines.append("")
|
||
lines.append("## Totals after migration")
|
||
lines.append("")
|
||
lines.append("| | Validators | Coins | Total |")
|
||
lines.append("|---|---:|---:|---:|")
|
||
lines.append(f"| Original holders (people) | {val_total-len(treasury)} | {coin_total} | 50 |")
|
||
lines.append(f"| Treasury completion | {len(treasury)} | 0 | {len(treasury)} |")
|
||
lines.append(f"| **On-chain today** | **{val_total}** | **{coin_total}** | **118** |")
|
||
lines.append(f"| Authorized (ETH Drop) | 100 | 11,111 | 11,211 + 1,000 ATM/Card |")
|
||
lines.append("")
|
||
lines.append(f"Recorded `totalLuxLocked` on GenesisNFTs = **{lux(cc_total_locked)} LUX** "
|
||
f"(100 validators × 1B = {lux(cc_val_bond)} + 18 coins = {lux(cc_coin_bond)}).")
|
||
lines.append("")
|
||
MAP_MD.write_text("\n".join(lines) + "\n")
|
||
|
||
print("ALL CHECKS PASSED")
|
||
print(f" ETH minted .............. 50 (val {eth_val} / coin {eth_coin}), reserve {eth_reserve:,} LUX, {len(eth_holders)} holders")
|
||
print(f" C-Chain minted .......... 118 (val {val_total} / coin {coin_total}); people 50 / treasury {len(treasury)}")
|
||
print(f" C-Chain recorded lock ... {cc_total_locked:,} LUX (val {cc_val_bond:,} + coin {cc_coin_bond:,})")
|
||
print(f" validator serials ....... exactly 1..100 (no gaps/dupes)")
|
||
print(f" wrote ................... {OUT.relative_to(ROOT)}")
|
||
print(f" wrote ................... {MAP_MD.relative_to(ROOT)}")
|