Skip to content

Mainnet launch checklist — 24-48h to Day 0

Target launch: 2026-06-22 / 2026-06-23 (refreshed 2026-06-22). Audit is NOT in this checklist — that gate is being accepted publicly with appropriate disclaimer in the launch post. Every other risk below has a concrete owner / step.

Status: code-level mainnet readiness work is COMPLETE. Everything red below requires operator hands (keys, multisigs, DNS, fund movements). Things in green are already merged and need no code touch — just env wiring at deploy.


✅ Already shipped this week (no further action needed)

ItemPRStatus
Genesis-deploy orchestrator with mainnet guardrails#308, #309Refuses to run if chain ≠ 7628, testnet-key reuse, or hardcoded testnet GENESIS in domain Deploy.s.sol
Env-driven chain id / chain name in dex-app bridge + domainapp frontend#310Was hardcoded testnet, now env-driven with empty-string default for safety
Status-app standalone Railway service#308, #309Builds clean; testnet redeploy gets status.testnet.sanect.com
Airdrop indexer scaffold (Postgres + Ethereum + sanect indexers + API)#312One Railway service, three sequential jobs
Real Uniswap v2/v3 subgraph snapshot + eligibility derivation#315snapshot → eligibility → Merkle pipeline E2E runnable
AirdropClaim.sol contract + 18 forge tests#313Merkle proof + indexer attestation + linear 180d vest + owner pause/rotate/sweep
Operator runbooks chain IDs + hostnames mainnet-ready#314762877628, *.testnet.sanect.com*.sanect.com
Tokenomics yield disclosurePR #310Staking-rewards-on-locked-buckets disclosure (Cosmos-standard mechanism)
Mainnet pre-flight audit in CLAUDE.mdPR #310Per-service env-var matrix + outstanding blocker list
Day-0 Genesis Allocation design locked#311docs/airdrop-genesis-allocation.md (Uniswap baseline × multiplier × product-engagement unlock)

🔴 P0 — operator hands required, must finish before genesis block

1. Generate fresh mainnet deployer mnemonic (offline)

Risk: testnet DEPLOYER_PRIVATE_KEY=0x7cB61D… is committed in repo history and known to GitHub Actions / Railway. Reusing it on mainnet = total loss.

Action (~30 min, must be offline):

bash
# On an air-gapped or freshly-rebooted machine:
cast wallet new-mnemonic --words 24

# Write the 24 words on PAPER. Two copies. Store separately.
# Never iCloud / Notes / Telegram / Drive / etc.

# On the deploy host when ready:
read -s SANECT_MNEMONIC
export DEPLOYER_PRIVATE_KEY=$(cast wallet private-key --mnemonic "$SANECT_MNEMONIC")
unset SANECT_MNEMONIC

# Verify address matches what's on paper:
cast wallet address --private-key "$DEPLOYER_PRIVATE_KEY"

The genesis-deploy.sh orchestrator hard-refuses if this evaluates to the testnet address.

2. Generate 3 role-EOAs (Day 0; migrate to Safes in Week 2)

Risk: 0x7cB61D… currently controls 100% of testnet supply, all contract ownerships, treasury. Single compromise = total loss on mainnet too unless we split.

Decision (2026-06-22): Day 0 uses 3 separate EOAs, not Gnosis Safes. Safe Global UI doesn't natively support new chains — going live tomorrow with proper Safes would require self-hosting Safe Transaction Service (multi-day infra) and submitting a PR to Safe to add sanect (weeks). Pragmatic call: 3 hardware-wallet-backed EOAs Day 0, migrate to on-chain Safe contracts (or a minimal multisig) within Day 14.

This is the same path Berachain / Monad / Linea / etc. took at their alpha launches.

Action (~30 min): Generate 3 fresh mnemonics, separately from the deployer:

bash
# On the offline laptop, three separate clean sessions:
cast wallet new-mnemonic --words 24    # → genesis-treasury EOA
cast wallet new-mnemonic --words 24    # → protocol-governance EOA
cast wallet new-mnemonic --words 24    # → ops EOA

For each:

  • Write the 24 words on paper, two copies, separate physical locations
  • Import into a hardware wallet (Ledger / Trezor)
  • Different physical signers if possible (you + co-founder + ops person)
EOA roleHolds
genesis-treasury EOADistribution / vesting controller. Receives genesis SNCT, holds team/foundation/grants buckets. Targeted by GENESIS + TREASURY in domainapp/contracts/script/Deploy.s.sol.
protocol-governance EOAOwns ShieldedPool, can rotate verifier, owns Reserved registrar, owns AirdropClaim. Targeted by $POOL_OWNER in genesis-deploy.sh.
ops EOAHyperlane relayer key, R2 credentials rotation, future infra controls.

Then patch domainapp/contracts/script/Deploy.s.sol:24-25 — replace the hardcoded GENESIS and TREASURY constants with the genesis-treasury EOA address. The genesis-deploy orchestrator refuses to run until this is done.

Risk disclosure (must go in launch post): "Genesis ownership is held by 3 hardware-wallet-backed EOAs for the first 14 days while Safe multisig infrastructure is being deployed on sanect. Migration to on-chain Safe contracts by Day 14."

2b. Week-2 migration to multisig (P1 — within Day 14)

Within 14 days of launch:

  • Deploy Safe singleton + SafeProxyFactory contracts on sanect mainnet (the canonical Safe contracts are public, just need to be deployed once per chain)
  • Create 3 Safe proxies (3-of-5 / 3-of-5 / 2-of-3 thresholds)
  • Transfer ownership from each EOA to the corresponding Safe address
  • For vesting accounts: transfer vesting controller via the chain's vesting module
  • For ShieldedPool: call transferOwnership(safeAddr) from the protocol-governance EOA
  • For AirdropClaim: same — transferOwnership(safeAddr)
  • Retire EOAs (sweep any residual gas to the new Safes)

Until Safe Transaction Service supports sanect, signing happens via cast send directly into Safe.execTransaction with pre-signed messages from each signer. Painful for ops but secure. Switch to UI once Safe team accepts sanect into their supported-chains list.

3. Validator topology — ≥ 4 nodes on NVMe

Risk: 1 active validator = chain halts when its host crashes. Mainnet must launch with ≥ 4 geographically distributed validators on local NVMe (not Railway ZFS).

Action (12-24 hours):

  • Move primary off Railway → Vultr High Frequency or Hetzner. Use scripts/sanect-publish-rpc.sh wizard.
  • Stand up at least 3 more in different regions (Singapore + Frankfurt + US East minimum).
  • Each gets a fresh priv_validator_key.json (never copy keys).
  • Self-stake 1000 SNCT each from team allocation.
  • Verify all 4 active in /network/nodes.

4. Mainnet bootstrap: chain ID + denom + endpoints

Action (~1 hour):

  • Build mainnet genesis with NETWORK=mainnet bash scripts/localnet.sh. Verify:
    • chain_id = sanect_7628-1
    • evm_chain_id = 7628
    • bech32 = snct / snctvaloper
    • Mainnet governance (max_deposit_period = 14d, voting_period = 14d)
    • Mint params 7-20% inflation
    • min_self_delegation = 1000 SNCT
  • DNS / Cloudflare:
    • rpc.sanect.com → primary mainnet node
    • scan.sanect.com → mainnet Blockscout
    • app.sanect.com → staking-app
    • swap.sanect.com → dex-app
    • domain.sanect.com → domain-app
    • status.sanect.com → status-app (built-in mainnet defaults)
    • archive.sanect.com → snapshot publisher (separate node, PRUNING=nothing)
    • p2p.sanect.com → Cloudflare DNS-only CNAME → Railway TCP proxy
    • snapshots.sanect.com → R2 bucket public URL
  • Set all NEXT_PUBLIC_* env vars per the matrix in CLAUDE.md "🟡 Mainnet pre-flight audit (2026-06-21)" section.

5. Hyperlane ISM upgrade

Risk: testnet uses TrustedRelayerIsm trusting the deployer EOA. Compromised key = bridge drains.

Action (~1 hour via Hyperlane CLI):

  • Generate fresh keypair RELAYER_KEY. Fund with ~$50 native on both sanect mainnet and Ethereum mainnet.
  • Use Hyperlane CLI to deploy MessageIdMultisigIsm on Ethereum mainnet side trusting a 2-of-3 validator set you also stand up.
  • Update sanect-side ISM the same way (or leave testnet-grade if launch budget < $X — explicitly accept the risk in the launch post).
  • Restart hyperlane-relayer Docker with defaultSigner.key = RELAYER_KEY.
  • Verify a small bridge round-trip.

6. Genesis vesting accounts

Risk: Without vesting, the team's entire bucket is liquid on Day 0. Looks like a rug.

Action (during genesis-build step, before any block produces):

bash
# After NETWORK=mainnet localnet.sh creates initial genesis.json,
# for each vesting bucket:
sanectd genesis add-genesis-account \
  <role-eoa-addr> \
  <amount>asnct \
  --vesting-amount <vested-portion>asnct \
  --vesting-end-time $(date -d "+4 years" +%s) \
  --home /data/.sanectd

Buckets per docs/tokenomics.md (operator finalizes percentages):

BucketCliffLinear after cliff
Foundationnone48 months
Team12 months36 months
Genesis Validator Grants1 month12 months
Treasurygov-controlledcontinuous
Liquidity12 monthsnone after
Airdropper-phaseheld by Merkle drop

7. Run the Genesis deploy orchestrator

Prerequisites — all of these must be true before this step works:

  1. Chain is alive and producing blocks — Step 6 has built genesis and started the primary validator. Verify with:
    bash
    curl -s <primary-rpc>/rpc/status | jq '.result.sync_info.latest_block_height'
    # expect a number > 0 that increases on a re-query
  2. Deployer EOA has SNCT for gas. The deployer wasn't in the genesis allocation — its balance is zero at chain start. Send it ~10 SNCT from the genesis-treasury role-EOA (which received its bucket at genesis):
    bash
    # From the genesis-treasury EOA (hardware wallet signs):
    cast send <deployer-address> --value 10ether \
      --rpc-url <primary-rpc> \
      --private-key $GENESIS_TREASURY_PRIVATE_KEY  # or use the hardware wallet via Frame

RPC endpoint — DNS not required. Use whichever URL is up first:

OptionWhen usable
Primary validator's Railway URL (e.g. https://privacy-layer1-production.up.railway.app)The moment the service boots — fastest path
Primary validator's public IP:port (http://1.2.3.4:8080)Same, no DNS dependency
https://rpc.sanect.comOnly after Cloudflare CNAME is pointed in Step 5

Pick the first one that works; you can re-export to the friendly URL later.

Then run:

bash
export RPC_URL=<primary-rpc>                       # whichever URL is alive
export SAFE_ADDRESS=0x<protocol-governance-eoa>    # Day 0 role-EOA; var name kept for backwards compat
export POOL_OWNER=$SAFE_ADDRESS
bash scripts/mainnet/genesis-deploy.sh 2>&1 | tee genesis-deploy-$(date +%s).log

Orchestrator deploys: Poseidon2 → HonkVerifier → ShieldedPool v1.4 → WSNCT → Factory → Router → TreasuryEmitter → MasterChef → full .snct registrar suite. Captures every address into genesis-addresses.json. Verifies pool wiring post-deploy.

Commit genesis-addresses.json + update CLAUDE.md's deployed-addresses table.


🟡 P1 — should be done by Day 0 but chain runs without

8. Public repo push

bash
cd public-repo && git init && git remote add origin [email protected]:sanect/sanect-node.git
git add -A && git commit -m "Initial public release: sanect node v1.0.0"
git push -u origin main

9. Faucet retiring

Leave FAUCET_PRIVATE_KEY unset on the mainnet staking-app Railway service. The UI's /api/faucet endpoint detects this and surfaces "contact the team" cleanly.

10. Snapshot publisher repointing

  • New Railway service mainnet-snapshot-publisher:
    • CHAIN_ID=sanect_7628-1 EVM_CHAIN_ID=7628 PRUNING=nothing SEED_NODE_URL=https://rpc.sanect.com
    • R2_BUCKET=sanect-snapshots-mainnet R2_PUBLIC_URL=https://snapshots.sanect.com
  • Verify LATEST.json after first 6h cron.

11. Status app deploy (2 services)

  • status.sanect.com — deploy status-app/ from main, no env overrides (mainnet defaults baked in)
  • status.testnet.sanect.com — same code, override the 4 NEXT_PUBLIC_*_URL env vars to testnet hostnames so testnet operators still have visibility

🟢 P2 — within Week 1, not blocking launch

  • Airdrop indexer + claim contract going live (snapshot + eligibility runs can take 1-3h; claim contract deploys after Merkle root finalized)
  • Validator recruitment for the full 50-node Variant A
  • LP pair seeding on mainnet (docs/seed-dex-pairs.md against mainnet addresses)
  • Audit kickoff (Zellic / Veridise / Trail of Bits engagement)

❌ Explicitly NOT shipping at launch

  • External audit (4-8 week process — launch acknowledged as unaudited)
  • LayerZero V2 OFT (3-5 month onboarding)
  • Squid Router integration (apply after mainnet stable 30 days)
  • Validator BLS aggregation / sub-200ms blocks (Phase 5/6 protocol work)
  • Limit orders on DEX (v2 feature)

Launch-day announcements

Pre-write these 24h before launch so they go out clean:

  1. Mainnet launch post on blog + X + Telegram
    • Highlights: privacy + 400ms blocks + EVM-compatible + .snct names + Hyperlane bridge
    • Disclose audit status honestly with roadmap link
    • MetaMask add-network: Chain ID 7628, RPC https://rpc.sanect.com, Symbol SNCT, Explorer https://scan.sanect.com
  2. Validator recruitment thread — link to operators/ runbooks (now mainnet-ready per PR #314)
  3. Airdrop policy — Genesis Allocation snapshot taken at last UTC midnight before mainnet genesis. Locked design in docs/airdrop-genesis-allocation.md. Claim opens when indexer publishes Merkle root.
  4. First 4-phase snapshot pre-commitment — sign + post the Day 90 target block height before users can game it

Owners (fill in)

ItemOwnerStatusDeadline
Generate fresh deployer mnemonic-24h
Generate 3 role-EOAs (hardware wallets)-24h
Migrate role-EOAs → on-chain Safes (P1)+14d post-launch
Patch domainapp/contracts/script/Deploy.s.sol GENESIS/TREASURY-24h
Validators on NVMe (≥4 nodes)-24h
Mainnet genesis + DNS + Cloudflare-12h
Hyperlane ISM upgrade + relayer rotation-12h
Genesis vesting accounts created-6h (before first block)
Run genesis-deploy.sh orchestrator-6h
Public repo push to sanect/sanect-node-6h
Snapshot publisher mainnet service-3h
Status app deploy (mainnet + testnet)-3h
Faucet env confirmed unset on mainnet-3h
Launch post drafted + scheduled-24h
Airdrop snapshot run + eligibility + Merkle-12h
Airdrop claim contract deploy + fund+24h post-launch