Skip to content

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

  1. Railway → your project → + NewDeploy from GitHub reposanect/sanect-node
  2. SettingsRoot Directory = (empty — uses repo root)
  3. SettingsBuildDockerfile Path = docker/Dockerfile
  4. 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=1000

Genesis accounts (NEW — what testnet didn't have)

GENESIS_TREASURY_ADDR=0x40DD390c690a606077C3A58a371Ca52778370A20
GENESIS_TREASURY_AMOUNT=969000000000000000000000000

FOUNDATION_ADDR=0xd5f05a16329c73efD21a57145A97c2b307E2dFED
FOUNDATION_AMOUNT=30000000000000000000000000
FOUNDATION_VEST_MONTHS=48

The 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 SNCT

If 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=2

Block production has started. Verify externally:

bash
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:

  1. Fund the deployer with 10 SNCT from the genesis-treasury hardware wallet
  2. Run scripts/mainnet/genesis-deploy.sh from your laptop targeting RPC_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.