From a46c38741e73c4f3f97c1c54b1294dc968d2ac83 Mon Sep 17 00:00:00 2001 From: Hanzo AI Date: Fri, 5 Jun 2026 15:33:52 -0700 Subject: [PATCH] WIP: session checkpoint 2026-06-05 (codec rip in flight) --- compose.yml | 286 +++++++++++++++++++++++++++++++++------- docker-compose.full.yml | 153 --------------------- docker-compose.ghcr.yml | 95 ------------- docker-compose.yml | 270 ------------------------------------- governance.md | 127 ++++++++++++++++++ 5 files changed, 369 insertions(+), 562 deletions(-) delete mode 100644 docker-compose.full.yml delete mode 100644 docker-compose.ghcr.yml delete mode 100644 docker-compose.yml create mode 100644 governance.md diff --git a/compose.yml b/compose.yml index 8299f35..d4bdf35 100644 --- a/compose.yml +++ b/compose.yml @@ -1,72 +1,270 @@ version: '3.8' services: - hardhat-node: + # Anvil blockchain for local development + anvil: + image: ghcr.io/foundry-rs/foundry:latest + container_name: dao-anvil + command: | + anvil + --host 0.0.0.0 + --port 8545 + --chain-id 1337 + --accounts 10 + --balance 10000 + --gas-limit 30000000 + --no-cors + --block-time 2 + ports: + - "8545:8545" + networks: + - dao-network + healthcheck: + test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"] + interval: 5s + timeout: 3s + retries: 5 + + # Contract deployment service + contracts: build: context: ./contracts dockerfile: Dockerfile - ports: - - "8545:8545" + container_name: dao-contracts + depends_on: + anvil: + condition: service_healthy + environment: + - RPC_URL=http://anvil:8545 + - PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + - NETWORK=localhost volumes: - - ./contracts:/usr/src/app + - ./contracts:/app + - contract-artifacts:/app/artifacts + - contract-deployments:/app/deployments + networks: + - dao-network + command: | + sh -c " + echo 'Waiting for Anvil to be ready...' + sleep 5 + echo 'Compiling contracts...' + npx hardhat compile + echo 'Deploying contracts...' + npx hardhat run scripts/deploy-local.ts --network localhost + echo 'Contracts deployed!' + tail -f /dev/null + " + # PostgreSQL database for indexer + postgres: + image: ghcr.io/hanzoai/sql:latest + container_name: dao-postgres + environment: + - POSTGRES_USER=dao + - POSTGRES_PASSWORD=dao123 + - POSTGRES_DB=dao_indexer + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - dao-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dao"] + interval: 5s + timeout: 5s + retries: 5 + + # Redis for caching and queues + redis: + image: redis:7-alpine + container_name: dao-redis + ports: + - "6379:6379" + networks: + - dao-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + # Indexer service to index blockchain events + indexer: + build: + context: ./api/packages/indexer + dockerfile: Dockerfile + container_name: dao-indexer + depends_on: + anvil: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_healthy + contracts: + condition: service_started + environment: + - RPC_URL=http://anvil:8545 + - DATABASE_URL=postgresql://dao:dao123@postgres:5432/dao_indexer + - REDIS_URL=redis://redis:6379 + - CHAIN_ID=1337 + - START_BLOCK=0 + - CONFIRMATION_BLOCKS=1 + volumes: + - ./api/packages/indexer:/app + - contract-artifacts:/contracts/artifacts:ro + - contract-deployments:/contracts/deployments:ro + networks: + - dao-network + command: npm run start:dev + + # API backend server api: build: context: ./api/packages/offchain dockerfile: Dockerfile + container_name: dao-api + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + indexer: + condition: service_started + environment: + - NODE_ENV=development + - PORT=4000 + - DATABASE_URL=postgresql://dao:dao123@postgres:5432/dao_indexer + - REDIS_URL=redis://redis:6379 + - RPC_URL=http://anvil:8545 + - CHAIN_ID=1337 + - CORS_ORIGIN=http://localhost:3000 ports: - "4000:4000" - depends_on: - - hardhat-node volumes: - - ./api:/usr/src/app + - ./api/packages/offchain:/app + - contract-artifacts:/contracts/artifacts:ro + - contract-deployments:/contracts/deployments:ro + networks: + - dao-network + command: npm run dev + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:4000/health"] + interval: 10s + timeout: 5s + retries: 5 - app: - build: - context: ./app - dockerfile: Dockerfile + # Subgraph node for GraphQL queries (optional) + graph-node: + image: graphprotocol/graph-node:latest + container_name: dao-graph-node + depends_on: + postgres: + condition: service_healthy + anvil: + condition: service_healthy + environment: + postgres_host: postgres + postgres_user: dao + postgres_pass: dao123 + postgres_db: dao_indexer + ipfs: 'ipfs:5001' + ethereum: 'localhost:http://anvil:8545' + GRAPH_LOG: info ports: - - "3000:3000" - depends_on: - - api - volumes: - - ./app:/usr/src/app + - "8000:8000" + - "8001:8001" + - "8020:8020" + - "8030:8030" + - "8040:8040" + networks: + - dao-network + # IPFS for decentralized storage ipfs: - image: ipfs/go-ipfs:latest + image: ipfs/kubo:latest + container_name: dao-ipfs ports: - "5001:5001" - "8080:8080" - - postgres: - image: ghcr.io/hanzoai/sql:latest - ports: - - "5432:5432" - environment: - POSTGRES_USER: graph-node - POSTGRES_PASSWORD: password - POSTGRES_DB: graph-node volumes: - - postgres-data:/var/lib/postgresql/data + - ipfs-data:/data/ipfs + networks: + - dao-network - graph-node: - image: graphprotocol/graph-node:latest - ports: - - "8000:8000" # GraphQL HTTP - - "8020:8020" # Admin RPC - - "8030:8030" # Indexer service - - "8040:8040" # JSON-RPC + # Frontend application + frontend: + build: + context: ./app + dockerfile: Dockerfile + container_name: dao-frontend depends_on: - - postgres - - hardhat-node - - ipfs + api: + condition: service_healthy + contracts: + condition: service_started environment: - postgres_host: postgres - postgres_user: graph-node - postgres_pass: password - postgres_db: graph-node - ipfs: "http://ipfs:5001" - ethereum: "mainnet:http://hardhat-node:8545" + - VITE_APP_CHAIN_ID=1337 + - VITE_APP_RPC_URL=http://localhost:8545 + - VITE_APP_API_URL=http://localhost:4000 + - VITE_APP_SUBGRAPH_URL=http://localhost:8000/subgraphs/name/lux/dao + - VITE_APP_IPFS_GATEWAY=http://localhost:8080 + - VITE_WALLETCONNECT_PROJECT_ID= + - VITE_ALCHEMY_API_KEY= + ports: + - "3000:3000" + volumes: + - ./app:/app + - contract-artifacts:/app/contracts/artifacts:ro + - contract-deployments:/app/contracts/deployments:ro + networks: + - dao-network + command: npm run dev + + # Monitoring with Prometheus (optional) + prometheus: + image: prom/prometheus:latest + container_name: dao-prometheus + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + ports: + - "9090:9090" + networks: + - dao-network + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + + # Grafana for visualization (optional) + grafana: + image: grafana/grafana:latest + container_name: dao-grafana + depends_on: + - prometheus + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + ports: + - "3001:3000" + volumes: + - grafana-data:/var/lib/grafana + - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards + - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources + networks: + - dao-network + +networks: + dao-network: + driver: bridge volumes: - postgres-data: \ No newline at end of file + postgres-data: + redis-data: + ipfs-data: + contract-artifacts: + contract-deployments: + prometheus-data: + grafana-data: \ No newline at end of file diff --git a/docker-compose.full.yml b/docker-compose.full.yml deleted file mode 100644 index 595e481..0000000 --- a/docker-compose.full.yml +++ /dev/null @@ -1,153 +0,0 @@ -version: '3.8' - -services: - # Local Blockchain - anvil: - image: ghcr.io/foundry-rs/foundry:latest - container_name: luxdao-anvil - entrypoint: ["anvil"] - command: ["--host", "0.0.0.0", "--chain-id", "1337", "--accounts", "10", "--block-time", "3"] - ports: - - "8545:8545" - networks: - - luxdao - healthcheck: - test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"] - interval: 5s - timeout: 3s - retries: 5 - - # PostgreSQL Database - postgres: - image: ghcr.io/hanzoai/sql:latest - container_name: luxdao-postgres - environment: - POSTGRES_USER: luxdao - POSTGRES_PASSWORD: luxdao123 - POSTGRES_DB: luxdao - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - luxdao - healthcheck: - test: ["CMD-SHELL", "pg_isready -U luxdao"] - interval: 5s - timeout: 3s - retries: 5 - - # Redis Cache - redis: - image: redis:7-alpine - container_name: luxdao-redis - ports: - - "6379:6379" - volumes: - - redis_data:/data - networks: - - luxdao - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 5 - - # IPFS Node - ipfs: - image: ipfs/kubo:latest - container_name: luxdao-ipfs - environment: - IPFS_PROFILE: server - ports: - - "5001:5001" - - "8080:8080" - volumes: - - ipfs_data:/data/ipfs - networks: - - luxdao - healthcheck: - test: ["CMD", "ipfs", "id"] - interval: 5s - timeout: 3s - retries: 5 - - # Smart Contract Deployment - contracts: - build: - context: ./contracts - dockerfile: Dockerfile - container_name: luxdao-contracts - depends_on: - anvil: - condition: service_healthy - environment: - RPC_URL: http://anvil:8545 - volumes: - - ./contracts/deployments:/app/deployments - networks: - - luxdao - command: ["npm", "run", "deploy:local"] - - # API Service - api: - build: - context: ./api/packages/dao-offchain - dockerfile: ../../../api/Dockerfile - container_name: luxdao-api - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - contracts: - condition: service_completed_successfully - environment: - DATABASE_URL: postgresql://luxdao:luxdao123@postgres:5432/luxdao - REDIS_URL: redis://redis:6379 - PORT: 3005 - PONDER_RPC_URL_1: http://anvil:8545 - ports: - - "3005:3005" - networks: - - luxdao - command: ["bun", "run", "start"] - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3005/"] - interval: 5s - timeout: 3s - retries: 10 - - # Frontend Application - frontend: - build: - context: ./app - dockerfile: Dockerfile - target: production - container_name: luxdao-frontend - depends_on: - api: - condition: service_healthy - contracts: - condition: service_completed_successfully - environment: - VITE_API_URL: http://api:3005 - VITE_RPC_URL: http://anvil:8545 - ports: - - "3000:3000" - networks: - - luxdao - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/"] - interval: 5s - timeout: 3s - retries: 10 - -networks: - luxdao: - driver: bridge - -volumes: - postgres_data: - redis_data: - ipfs_data: \ No newline at end of file diff --git a/docker-compose.ghcr.yml b/docker-compose.ghcr.yml deleted file mode 100644 index 6a71cb2..0000000 --- a/docker-compose.ghcr.yml +++ /dev/null @@ -1,95 +0,0 @@ -version: '3.8' - -services: - # Infrastructure Services - anvil: - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: ["anvil"] - command: [ - "--host", "0.0.0.0", - "--chain-id", "1337", - "--block-time", "12", - "--accounts", "10", - "--balance", "10000" - ] - ports: - - "8546:8545" - networks: - - dao-network - healthcheck: - test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"] - interval: 10s - timeout: 5s - retries: 5 - - postgres: - image: ghcr.io/hanzoai/sql:latest - environment: - POSTGRES_USER: luxdao - POSTGRES_PASSWORD: luxdao123 - POSTGRES_DB: luxdao - ports: - - "5433:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - networks: - - dao-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U luxdao"] - interval: 10s - timeout: 5s - retries: 5 - - redis: - image: redis:7-alpine - ports: - - "6380:6379" - networks: - - dao-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - # Application Services - contracts: - image: ghcr.io/luxfi/dao-contracts:v1.0.0 - depends_on: - anvil: - condition: service_healthy - environment: - RPC_URL: http://anvil:8545 - DEPLOYER_PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" - networks: - - dao-network - volumes: - - contracts-artifacts:/app/artifacts - - contracts-deployments:/app/deployments - - app: - image: ghcr.io/luxfi/dao-frontend:v1.0.0 - ports: - - "3000:3000" - environment: - VITE_API_URL: http://localhost:3001 - VITE_RPC_URL: http://localhost:8546 - VITE_CHAIN_ID: 1337 - depends_on: - - contracts - networks: - - dao-network - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"] - interval: 10s - timeout: 5s - retries: 5 - -networks: - dao-network: - driver: bridge - -volumes: - postgres-data: - contracts-artifacts: - contracts-deployments: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index d4bdf35..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,270 +0,0 @@ -version: '3.8' - -services: - # Anvil blockchain for local development - anvil: - image: ghcr.io/foundry-rs/foundry:latest - container_name: dao-anvil - command: | - anvil - --host 0.0.0.0 - --port 8545 - --chain-id 1337 - --accounts 10 - --balance 10000 - --gas-limit 30000000 - --no-cors - --block-time 2 - ports: - - "8545:8545" - networks: - - dao-network - healthcheck: - test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"] - interval: 5s - timeout: 3s - retries: 5 - - # Contract deployment service - contracts: - build: - context: ./contracts - dockerfile: Dockerfile - container_name: dao-contracts - depends_on: - anvil: - condition: service_healthy - environment: - - RPC_URL=http://anvil:8545 - - PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - - NETWORK=localhost - volumes: - - ./contracts:/app - - contract-artifacts:/app/artifacts - - contract-deployments:/app/deployments - networks: - - dao-network - command: | - sh -c " - echo 'Waiting for Anvil to be ready...' - sleep 5 - echo 'Compiling contracts...' - npx hardhat compile - echo 'Deploying contracts...' - npx hardhat run scripts/deploy-local.ts --network localhost - echo 'Contracts deployed!' - tail -f /dev/null - " - - # PostgreSQL database for indexer - postgres: - image: ghcr.io/hanzoai/sql:latest - container_name: dao-postgres - environment: - - POSTGRES_USER=dao - - POSTGRES_PASSWORD=dao123 - - POSTGRES_DB=dao_indexer - ports: - - "5432:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - networks: - - dao-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U dao"] - interval: 5s - timeout: 5s - retries: 5 - - # Redis for caching and queues - redis: - image: redis:7-alpine - container_name: dao-redis - ports: - - "6379:6379" - networks: - - dao-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 5 - - # Indexer service to index blockchain events - indexer: - build: - context: ./api/packages/indexer - dockerfile: Dockerfile - container_name: dao-indexer - depends_on: - anvil: - condition: service_healthy - postgres: - condition: service_healthy - redis: - condition: service_healthy - contracts: - condition: service_started - environment: - - RPC_URL=http://anvil:8545 - - DATABASE_URL=postgresql://dao:dao123@postgres:5432/dao_indexer - - REDIS_URL=redis://redis:6379 - - CHAIN_ID=1337 - - START_BLOCK=0 - - CONFIRMATION_BLOCKS=1 - volumes: - - ./api/packages/indexer:/app - - contract-artifacts:/contracts/artifacts:ro - - contract-deployments:/contracts/deployments:ro - networks: - - dao-network - command: npm run start:dev - - # API backend server - api: - build: - context: ./api/packages/offchain - dockerfile: Dockerfile - container_name: dao-api - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - indexer: - condition: service_started - environment: - - NODE_ENV=development - - PORT=4000 - - DATABASE_URL=postgresql://dao:dao123@postgres:5432/dao_indexer - - REDIS_URL=redis://redis:6379 - - RPC_URL=http://anvil:8545 - - CHAIN_ID=1337 - - CORS_ORIGIN=http://localhost:3000 - ports: - - "4000:4000" - volumes: - - ./api/packages/offchain:/app - - contract-artifacts:/contracts/artifacts:ro - - contract-deployments:/contracts/deployments:ro - networks: - - dao-network - command: npm run dev - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:4000/health"] - interval: 10s - timeout: 5s - retries: 5 - - # Subgraph node for GraphQL queries (optional) - graph-node: - image: graphprotocol/graph-node:latest - container_name: dao-graph-node - depends_on: - postgres: - condition: service_healthy - anvil: - condition: service_healthy - environment: - postgres_host: postgres - postgres_user: dao - postgres_pass: dao123 - postgres_db: dao_indexer - ipfs: 'ipfs:5001' - ethereum: 'localhost:http://anvil:8545' - GRAPH_LOG: info - ports: - - "8000:8000" - - "8001:8001" - - "8020:8020" - - "8030:8030" - - "8040:8040" - networks: - - dao-network - - # IPFS for decentralized storage - ipfs: - image: ipfs/kubo:latest - container_name: dao-ipfs - ports: - - "5001:5001" - - "8080:8080" - volumes: - - ipfs-data:/data/ipfs - networks: - - dao-network - - # Frontend application - frontend: - build: - context: ./app - dockerfile: Dockerfile - container_name: dao-frontend - depends_on: - api: - condition: service_healthy - contracts: - condition: service_started - environment: - - VITE_APP_CHAIN_ID=1337 - - VITE_APP_RPC_URL=http://localhost:8545 - - VITE_APP_API_URL=http://localhost:4000 - - VITE_APP_SUBGRAPH_URL=http://localhost:8000/subgraphs/name/lux/dao - - VITE_APP_IPFS_GATEWAY=http://localhost:8080 - - VITE_WALLETCONNECT_PROJECT_ID= - - VITE_ALCHEMY_API_KEY= - ports: - - "3000:3000" - volumes: - - ./app:/app - - contract-artifacts:/app/contracts/artifacts:ro - - contract-deployments:/app/contracts/deployments:ro - networks: - - dao-network - command: npm run dev - - # Monitoring with Prometheus (optional) - prometheus: - image: prom/prometheus:latest - container_name: dao-prometheus - volumes: - - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml - - prometheus-data:/prometheus - ports: - - "9090:9090" - networks: - - dao-network - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - # Grafana for visualization (optional) - grafana: - image: grafana/grafana:latest - container_name: dao-grafana - depends_on: - - prometheus - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_USERS_ALLOW_SIGN_UP=false - ports: - - "3001:3000" - volumes: - - grafana-data:/var/lib/grafana - - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards - - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources - networks: - - dao-network - -networks: - dao-network: - driver: bridge - -volumes: - postgres-data: - redis-data: - ipfs-data: - contract-artifacts: - contract-deployments: - prometheus-data: - grafana-data: \ No newline at end of file diff --git a/governance.md b/governance.md new file mode 100644 index 0000000..d2fd1ed --- /dev/null +++ b/governance.md @@ -0,0 +1,127 @@ +# Lux Governance — Multisig Signer Policy + +**Status**: Active 2026-06-05 → next rotation TBD +**Owners of this doc**: Lux Industries protocol governance + +## Scope + +This document is the canonical record of the launch-time multisig signer +configuration for production Safe deployments across the Lux network of +blockchains (Lux primary, Hanzo L2, Zoo L2, Pars L2, SPC L2, Osage L2 and +all corresponding testnet/devnet pairs). + +It is the **only** place the signer derivation, threshold, and rotation +plan live. Any code that constructs a launch-time Safe (notably +`lux/standard/contracts/script/DeploySafe.s.sol`) reads these constants +back from this doc — when they change, the doc changes first. + +## Launch-time signer set + +| Slot | Derivation path (BIP44, ETH cointype 60) | Role | +|------|------------------------------------------|------| +| 0 | `m/44'/60'/0'/0/0` (LUX_MNEMONIC) | Founder operator (deployer) | +| 1 | `m/44'/60'/0'/0/1` (LUX_MNEMONIC) | Co-founder operator | +| 2 | `m/44'/60'/0'/0/2` (LUX_MNEMONIC) | Security lead | +| 3 | `m/44'/60'/0'/0/3` (LUX_MNEMONIC) | Treasury / operations lead | +| 4 | `m/44'/60'/0'/0/4` (LUX_MNEMONIC) | Engineering lead | + +All five slots derive from the **same** `LUX_MNEMONIC` seed for launch. +This is intentional and time-bounded: launch-time signers exist to +deploy the rest of the stack and immediately hand off control. Each +hardware-key holder will replace their derived slot via the standard +Safe owner-management flow (`addOwnerWithThreshold` → `removeOwner` or +`swapOwner`) once the deploy completes. + +## Threshold + +- **Production multisigs (mainnet)**: **3 of 5** (default) +- **Testnet / devnet multisigs**: **2 of 5** is acceptable, set via + `SAFE_THRESHOLD=2` at deploy time. +- Hard ceiling: threshold ≤ owner count (`SIGNER_COUNT = 5`). +- Threshold ≥ 1. + +`DeploySafe.s.sol` reads `SAFE_THRESHOLD` from env; default is 3. + +## Deterministic predictability + +The Safe proxy address per (brand, env, chainId) is deterministic by +construction: + +``` +saltNonce = uint256(keccak256(abi.encode(BRAND, SAFE_ENV, block.chainid))) +proxy = SafeProxyFactory.createProxyWithNonce(singleton, setupCalldata, saltNonce) +``` + +so re-running the deploy script against the same `(brand, env, +chainId)` produces the **same proxy address** (the second call will +revert with the upstream Safe "creation failed" error rather than +overwriting). This is the intended behaviour — it gives reviewers a +way to pre-compute proxy addresses for spec docs without trusting any +live broadcast. + +## Rotation plan (post-launch, before mainnet bridge activation) + +1. Each of the five hardware-key holders signs a registration message + committing their hardware-derived address to a public proof file at + `~/work/lux/dao/signers/.proof`. +2. The Safe is reconfigured **per-brand**: each brand's Safe receives a + per-brand owner set drawn from the same five hardware keys, possibly + with brand-specific advisor seats added. +3. After every brand's Safe is rotated, the launch-time derived + addresses are removed via `removeOwner`. The launch-time mnemonic + key chain is then no longer authoritative for any Safe. + +Rotation must happen **before** the BridgeV4 activation timestamp on +mainnet (≥ 2026-08-08 per `~/work/lux/genesis/configs/mainnet/upgrade.json`). +If the rotation is not complete, BridgeV4 activation must be deferred — +a five-launch-key Safe is fine for protocol bringup but not for +holding bridge fee receivership in production steady-state. + +## Per-chain Safe deployment manifest + +Each (brand, env) Safe address is recorded in: + +``` +~/work/lux/standard/deployments/brand-l1-/.json + .contracts.Safe +``` + +The same Safe is used as the `protocolFeeController` in +`~/work/lux/genesis/configs/mainnet/upgrade.json` once activation lands. + +## Why not Snowdrop-style governance now + +A Snowdrop-style on-chain proposal flow with `Timelock + vLUX + GaugeController` +already exists in the Standard contract set (`DeployMultiNetwork.s.sol` +Phase 5) and is intentionally deployed **alongside** the Safe. The Safe +is the operational signer for protocol upgrades during the rotation +window; the Timelock + vLUX path becomes the authoritative governance +route once token distribution lands and quorum is reachable. Until +quorum is reachable, a 3-of-5 Safe is the right amount of friction. + +## Anti-patterns explicitly avoided + +- **Single-key admin** — never write `admin = deployer` in production + contracts. Always rotate to the Safe immediately after deploy. +- **Threshold < ⌈n/2⌉** — minority threshold is collusion-friendly. We + default 3-of-5 and refuse to encode 2-of-5 in production code. +- **Per-app mnemonics** — one `LUX_MNEMONIC` for launch derivation. No + per-app seeds (those just multiply the number of single-key compromise + surfaces). +- **Hot-wallet signers on mainnet** — launch-time derived slots get + removed before bridge mainnet activation. + +## Verification + +Anyone with `LUX_MNEMONIC` and `forge` can independently reproduce all +five launch-time signers: + +```bash +for i in 0 1 2 3 4; do + cast wallet address \ + --mnemonic "$LUX_MNEMONIC" \ + --mnemonic-index "$i" +done +``` + +These addresses must match what is logged by `DeploySafe.s.sol`.