Mainnet primary on Railway — fast path
The bootstrap-primary.sh route runs on a dedicated NVMe VPS. This guide is the simpler alternative: deploy the primary validator on Railway, same shape as the testnet deploy.
Trade-off vs the NVMe path:
- ✅ No SSH, no Linux ops, no Docker manual setup
- ✅ Same flow you already used for testnet
- ❌ Block time ~720ms instead of ~420ms (Railway ZFS shared pool — see CLAUDE.md "Block-time floor on Railway")
- ❌ Single point of failure on Railway's infra
For a Day-0 launch with low load, the slower block time is invisible. Migrate to NVMe in Week 1 if you want.
1. Create the Railway service
- Railway → your project → + New → Deploy from GitHub repo →
sanect/sanect-node - Settings → Root Directory = (empty — uses repo root)
- Settings → Build → Dockerfile Path =
docker/Dockerfile - Attach a volume at
/data(Railway → Volumes → +). At least 50 GB for mainnet's growth.
2. Set env vars
Service → Variables tab → + New Variable for each:
Chain identity
NETWORK=mainnet
CHAIN_ID=sanect_7628-1
EVM_CHAIN_ID=7628
HOMEDIR=/data/.sanectd
DENOM=asnct
DISPLAY=snct
MONIKER=sanect-mainnet-primary
MIN_SELF_DELEGATION=1000Genesis accounts (NEW — what testnet didn't have)
GENESIS_TREASURY_ADDR=0x40DD390c690a606077C3A58a371Ca52778370A20
GENESIS_TREASURY_AMOUNT=969000000000000000000000000
FOUNDATION_ADDR=0xd5f05a16329c73efD21a57145A97c2b307E2dFED
FOUNDATION_AMOUNT=30000000000000000000000000
FOUNDATION_VEST_MONTHS=48The entrypoint reads these on first boot, converts the EVM hex → bech32 internally, adds the accounts to genesis, then refuses to start the chain if the bank balances don't sum to exactly 1B SNCT.
Networking
EXTERNAL_P2P_ADDRESS=p2p.sanect.com:46656(After the chain is alive, point your Cloudflare CNAME p2p.sanect.com at Railway's TCP proxy in DNS-only / grey cloud mode.)
3. Deploy
Click Deploy. Watch the logs for these lines:
>>> First boot: generating genesis for sanect_7628-1
>>> Adding genesis-treasury: snct1... 970000000000000000000000000 asnct (no vest)
>>> Adding Foundation: snct1... 30000000000000000000000000 asnct (48-mo vest)
>>> ✓ genesis bank balances sum to 1B SNCTIf the totals don't match, the entrypoint refuses to start the chain — the service will crash-loop until you fix the amounts and redeploy with a fresh volume.
Then:
INFO Starting Node
INFO Committed state height=1
INFO Committed state height=2Block production has started. Verify externally:
curl -s https://<your-railway-url>/rpc/status | jq '{
height: .result.sync_info.latest_block_height,
chain_id: .result.node_info.network
}'Expect height > 0 and incrementing, chain_id: "sanect_7628-1".
4. Custom domain
Cloudflare CNAME rpc.sanect.com → Railway service URL (orange cloud, proxied).
5. Next step
Same as the NVMe path:
- Fund the deployer with 10 SNCT from the genesis-treasury hardware wallet
- Run
scripts/mainnet/genesis-deploy.shfrom your laptop targetingRPC_URL=https://rpc.sanect.com
What to watch out for
- Fresh volume only. The entrypoint only runs genesis-account commands on first boot (empty volume). If you set the env vars but the volume already has a chain, they're ignored. Wipe via Railway → Volumes → Delete + Recreate.
- Don't duplicate the service. Copying a Railway service copies the volume, which copies the consensus key — double-sign jail within minutes. Each validator needs a fresh volume.
- Block time honesty. Per CLAUDE.md the Railway floor is ~720ms with multiple Railway validators. With just the primary it'll be ~420ms. As you join NVMe validators in Phase 2 of the launch checklist, the slower validators pull the average up — that's expected and acceptable for Day-0 launch.
When you're ready to migrate to NVMe
Stand up an NVMe validator with bootstrap-primary.sh-style flow but as a JOINER (JOIN_NETWORK=true), wait for it to sync, propose-and-vote a soft handover (e.g. the NVMe joins as a top validator while the Railway primary unbonds), then retire Railway. Operational, not a chain reset.