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)
| Item | PR | Status |
|---|---|---|
| Genesis-deploy orchestrator with mainnet guardrails | #308, #309 | Refuses 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 | #310 | Was hardcoded testnet, now env-driven with empty-string default for safety |
| Status-app standalone Railway service | #308, #309 | Builds clean; testnet redeploy gets status.testnet.sanect.com |
| Airdrop indexer scaffold (Postgres + Ethereum + sanect indexers + API) | #312 | One Railway service, three sequential jobs |
| Real Uniswap v2/v3 subgraph snapshot + eligibility derivation | #315 | snapshot → eligibility → Merkle pipeline E2E runnable |
AirdropClaim.sol contract + 18 forge tests | #313 | Merkle proof + indexer attestation + linear 180d vest + owner pause/rotate/sweep |
| Operator runbooks chain IDs + hostnames mainnet-ready | #314 | 76287 → 7628, *.testnet.sanect.com → *.sanect.com |
| Tokenomics yield disclosure | PR #310 | Staking-rewards-on-locked-buckets disclosure (Cosmos-standard mechanism) |
| Mainnet pre-flight audit in CLAUDE.md | PR #310 | Per-service env-var matrix + outstanding blocker list |
| Day-0 Genesis Allocation design locked | #311 | docs/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):
# 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:
# 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 EOAFor 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 role | Holds |
|---|---|
genesis-treasury EOA | Distribution / vesting controller. Receives genesis SNCT, holds team/foundation/grants buckets. Targeted by GENESIS + TREASURY in domainapp/contracts/script/Deploy.s.sol. |
protocol-governance EOA | Owns ShieldedPool, can rotate verifier, owns Reserved registrar, owns AirdropClaim. Targeted by $POOL_OWNER in genesis-deploy.sh. |
ops EOA | Hyperlane 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.shwizard. - 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-1evm_chain_id = 7628bech32 = 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 nodescan.sanect.com→ mainnet Blockscoutapp.sanect.com→ staking-appswap.sanect.com→ dex-appdomain.sanect.com→ domain-appstatus.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 proxysnapshots.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
MessageIdMultisigIsmon 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-relayerDocker withdefaultSigner.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):
# 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/.sanectdBuckets per docs/tokenomics.md (operator finalizes percentages):
| Bucket | Cliff | Linear after cliff |
|---|---|---|
| Foundation | none | 48 months |
| Team | 12 months | 36 months |
| Genesis Validator Grants | 1 month | 12 months |
| Treasury | gov-controlled | continuous |
| Liquidity | 12 months | none after |
| Airdrop | per-phase | held by Merkle drop |
7. Run the Genesis deploy orchestrator
Prerequisites — all of these must be true before this step works:
- 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 - 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:
| Option | When 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.com | Only 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:
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).logOrchestrator 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
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 main9. 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-1EVM_CHAIN_ID=7628PRUNING=nothingSEED_NODE_URL=https://rpc.sanect.comR2_BUCKET=sanect-snapshots-mainnetR2_PUBLIC_URL=https://snapshots.sanect.com
- Verify
LATEST.jsonafter first 6h cron.
11. Status app deploy (2 services)
status.sanect.com— deploystatus-app/from main, no env overrides (mainnet defaults baked in)status.testnet.sanect.com— same code, override the 4NEXT_PUBLIC_*_URLenv 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.mdagainst 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:
- Mainnet launch post on blog + X + Telegram
- Highlights: privacy + 400ms blocks + EVM-compatible +
.snctnames + 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
- Highlights: privacy + 400ms blocks + EVM-compatible +
- Validator recruitment thread — link to
operators/runbooks (now mainnet-ready per PR #314) - Airdrop policy — Genesis Allocation snapshot taken at
last UTC midnight before mainnet genesis. Locked design indocs/airdrop-genesis-allocation.md. Claim opens when indexer publishes Merkle root. - First 4-phase snapshot pre-commitment — sign + post the Day 90 target block height before users can game it
Owners (fill in)
| Item | Owner | Status | Deadline |
|---|---|---|---|
| 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 |